project_releaser 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 4f30d253fc85c95986faf137ae4dccd4de546fc4
4
+ data.tar.gz: d8dc32e00467b6710790f42ba6962f0f8eb5afec
5
+ SHA512:
6
+ metadata.gz: adaee8550f6562864b3cc98617d4292bbb6c3d99c93418fdf5c4c0b1e36e31f4f9a325b2a49fcee24afd884202644837fb2da06f5572e7f7e9cebe288e12ed39
7
+ data.tar.gz: edf3aa85e3b00c1ce5347b998e62b3c57460e762179f422418551b575a2009d949ff95e1227d6aee245073e5d21168476777a81202a01775528cced98f66e165
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ .vagrant
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --warnings
3
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,51 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ project_releaser (0.0.1)
5
+ colorize
6
+ commander
7
+ git
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ coderay (1.1.0)
13
+ colorize (0.7.7)
14
+ commander (4.3.4)
15
+ highline (~> 1.7.2)
16
+ diff-lcs (1.2.5)
17
+ git (1.2.9.1)
18
+ highline (1.7.2)
19
+ method_source (0.8.2)
20
+ pry (0.10.1)
21
+ coderay (~> 1.1.0)
22
+ method_source (~> 0.8.1)
23
+ slop (~> 3.4)
24
+ rake (10.4.2)
25
+ rspec (3.2.0)
26
+ rspec-core (~> 3.2.0)
27
+ rspec-expectations (~> 3.2.0)
28
+ rspec-mocks (~> 3.2.0)
29
+ rspec-core (3.2.3)
30
+ rspec-support (~> 3.2.0)
31
+ rspec-expectations (3.2.1)
32
+ diff-lcs (>= 1.2.0, < 2.0)
33
+ rspec-support (~> 3.2.0)
34
+ rspec-mocks (3.2.1)
35
+ diff-lcs (>= 1.2.0, < 2.0)
36
+ rspec-support (~> 3.2.0)
37
+ rspec-support (3.2.2)
38
+ slop (3.6.0)
39
+
40
+ PLATFORMS
41
+ ruby
42
+
43
+ DEPENDENCIES
44
+ bundler (~> 1.3)
45
+ project_releaser!
46
+ pry
47
+ rake
48
+ rspec
49
+
50
+ BUNDLED WITH
51
+ 1.10.3
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 kagux
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,48 @@
1
+ # Project Releaser
2
+
3
+ CLI command to make releases of your semantic versioned project a breeze.
4
+ It merges your development branch into release and tags with next `major`, `minor` or `patch` version.
5
+
6
+ Currently branches are not configurable:
7
+ * development is `staging`
8
+ * release is `master`
9
+
10
+ ## Installation
11
+
12
+ Add this line to your application's Gemfile:
13
+
14
+ gem 'project_releaser'
15
+
16
+ And then execute:
17
+
18
+ $ bundle install
19
+
20
+ ## Usage
21
+ ```
22
+ project [command]
23
+
24
+ --help Print help
25
+ ```
26
+
27
+ Available commands:
28
+
29
+ * `version` Current production version of the project
30
+ * `name` Project name
31
+ * `update` Updates master and staging branches from all reomtes
32
+ * `release` Merges staging into master and pushes master with new version
33
+
34
+
35
+ ### Release Command
36
+
37
+ By default it releases next `patch` version of the project. To set specific version use (`major`, `minor` or `patch`) argument
38
+ ```
39
+ project release major
40
+ ```
41
+ You can also release an exact version
42
+ ```
43
+ project release 7.10.5
44
+ ```
45
+ ## TODO
46
+
47
+ * Configurable branches
48
+ * Improve logs
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/Vagrantfile ADDED
@@ -0,0 +1,18 @@
1
+ # -*- mode: ruby -*-
2
+ # vi: set ft=ruby :
3
+
4
+ # Vagrantfile API/syntax version. Don't touch unless you know what you're doing!
5
+ VAGRANTFILE_API_VERSION = "2"
6
+
7
+ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
8
+ config.vm.box = "ubuntu/trusty64"
9
+ config.ssh.forward_agent = true
10
+
11
+ config.vm.provider "virtualbox" do |v|
12
+ v.memory = 2024
13
+ v.cpus = 2
14
+ end
15
+
16
+ config.vm.provision :shell, :inline => "/vagrant/vagrant/provision.sh #{`whoami`}"
17
+ end
18
+
data/bin/project ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'project_releaser'
4
+
5
+ ProjectReleaser.configure do |config|
6
+ config.logger = ProjectReleaser::Logger::ColoredConsole.new
7
+ end
8
+
9
+ ProjectReleaser::CLI.new.run
@@ -0,0 +1,85 @@
1
+ require 'commander'
2
+
3
+ module ProjectReleaser
4
+ class CLI
5
+ include Commander::Methods
6
+ include Logger::Loggable
7
+
8
+ def run
9
+ program :name, 'Project Releaser'
10
+ program :version, ProjectReleaser::VERSION
11
+ program :description, 'Painlessly release your SemVer project'
12
+ program :help_formatter, :compact
13
+ default_command :help
14
+
15
+ always_trace!
16
+
17
+ build_version_cmd
18
+ build_name_cmd
19
+ build_update_cmd
20
+ build_release_cmd
21
+
22
+ run!
23
+ rescue ProjectReleaser::Project::Repository::RepositoryHasNoBranches
24
+ logger.error "It appears your project '#{project.name}' has no branches, you need at least 2"
25
+ rescue ProjectReleaser::Project::Repository::RepositoryNotFound
26
+ logger.error 'Working directory does not have git repository'
27
+ rescue ProjectReleaser::Project::Repository::MissingBranch
28
+ logger.error 'Project repository does not have expected branch'
29
+ end
30
+
31
+ private
32
+
33
+ def build_version_cmd
34
+ command :version do |c|
35
+ c.syntax = 'project version'
36
+ c.description = 'Current version of the project'
37
+ action c do
38
+ logger.info "Current version of '#{project.name}' is #{project.current_version}"
39
+ end
40
+ end
41
+ end
42
+
43
+ def build_name_cmd
44
+ command :name do |c|
45
+ c.syntax = 'project name'
46
+ c.description = 'Infer project name from git remotes'
47
+ action c do
48
+ logger.info "You are working on project '#{project.name}'"
49
+ end
50
+ end
51
+ end
52
+
53
+ def build_update_cmd
54
+ command :update do |c|
55
+ c.syntax = 'project update'
56
+ c.description = 'Updates release and develop branches from all remotes'
57
+ action c do
58
+ ProjectReleaser::Project.update
59
+ end
60
+ end
61
+ end
62
+
63
+ def build_release_cmd
64
+ command :release do |c|
65
+ c.syntax = 'project release'
66
+ c.description = 'Merges develop into release and pushes it with new version tag'
67
+ action c do |args, options|
68
+ ProjectReleaser::Project.release args.first
69
+ end
70
+ end
71
+ end
72
+
73
+ def project
74
+ ProjectReleaser::Project
75
+ end
76
+
77
+ def action(cmd, &block)
78
+ proc = lambda do |args, options|
79
+ block.call args, options
80
+ logger.info '`Done!`'
81
+ end
82
+ cmd.action(&proc)
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,41 @@
1
+ require 'colorize'
2
+
3
+ module ProjectReleaser
4
+ module Logger
5
+ class ColoredConsole
6
+ COLORS = {
7
+ :light_blue => "'",
8
+ :green => '`',
9
+ :yellow => '"'
10
+ }
11
+
12
+ def info(msg)
13
+ puts colorize(msg)
14
+ end
15
+
16
+ def error(msg)
17
+ info "\"Error\": #{msg}"
18
+ end
19
+
20
+ private
21
+
22
+ def colorize(msg)
23
+ msg = color_all_words(msg, :light_white)
24
+ COLORS.each do |color, char|
25
+ msg = color_char(msg, char, color)
26
+ end
27
+ msg
28
+ end
29
+
30
+ def color_all_words(msg, color)
31
+ msg.split.map { |w| w.colorize(color) }.join(' ')
32
+ end
33
+
34
+ def color_char(msg, char, color)
35
+ color_regexp = Regexp.new "#{char}.*?#{char}"
36
+ cleanup_regexp = Regexp.new "#{char}"
37
+ msg.gsub(color_regexp) {|m| m.gsub(cleanup_regexp, '').colorize color}
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,12 @@
1
+ module ProjectReleaser
2
+ module Logger
3
+ class Console
4
+ def info(msg)
5
+ puts msg
6
+ end
7
+
8
+ alias_method :error, :info
9
+
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,9 @@
1
+ module ProjectReleaser
2
+ module Logger
3
+ module Loggable
4
+ def logger
5
+ ProjectReleaser.configuration.logger || Console.new
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,12 @@
1
+ module ProjectReleaser
2
+ module Logger
3
+ class Null
4
+ def info(msg)
5
+ # no op
6
+ end
7
+
8
+ alias_method :error, :info
9
+ end
10
+ end
11
+ end
12
+
@@ -0,0 +1,55 @@
1
+ module ProjectReleaser
2
+ module Project
3
+ class Info
4
+ def initialize(git)
5
+ @git = git
6
+ end
7
+
8
+ def name
9
+ url = @git.remotes.values.last
10
+ return 'unknown' if url.nil?
11
+
12
+ repo_name_start = url.index('/') + 1
13
+ url[repo_name_start..-1].sub(/\.git$/, '')
14
+ end
15
+
16
+ def current_version
17
+ @git.fetch_tags
18
+ format @git.current_version
19
+ end
20
+
21
+ def next_version(version_type = :patch)
22
+ version_type = (version_type ||= :patch).to_sym
23
+ return exact_version(version_type) unless valid_version_part? version_type
24
+
25
+ new_version = @git.current_version
26
+ new_version[version_type] += 1
27
+ new_version = reset_lesser_versions new_version, version_type
28
+ format new_version
29
+ end
30
+
31
+ private
32
+ def exact_version(version)
33
+ if version =~ /\Av{0,1}\d+\.\d+\.\d+\Z/
34
+ return version.to_s.prepend('v').sub('vv', 'v')
35
+ else
36
+ raise ArgumentError
37
+ end
38
+ end
39
+
40
+ def valid_version_part?(version)
41
+ Project::Repository::VERSION_PARTS.include? version
42
+ end
43
+
44
+ def format(version)
45
+ 'v' << version.values.join('.')
46
+ end
47
+
48
+ def reset_lesser_versions(full_version, cutoff_version)
49
+ keys = full_version.keys
50
+ index = keys.index cutoff_version
51
+ Hash[ full_version.map{|k, v| keys.index(k) <= index ? [k, v] : [k, 0]} ]
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,45 @@
1
+ module ProjectReleaser
2
+ module Project
3
+ class Releaser
4
+ include Logger::Loggable
5
+
6
+ def initialize(git)
7
+ @git = git
8
+ end
9
+
10
+ def release(version)
11
+ @git.returning_to_current_branch do |git|
12
+ @git = git
13
+ update_local_branches
14
+ merge_branches if branches.size > 1
15
+ push_release version
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def update_local_branches
22
+ logger.info "updating local branches"
23
+ @git.pull branches
24
+ end
25
+
26
+ def merge_branches
27
+ logger.info "merging 'staging' into 'master'"
28
+ @git.merge :master, :staging
29
+ end
30
+
31
+ def push_release(version)
32
+ logger.info "pushing new release `#{version}` to 'master'"
33
+ @git.push :master, version
34
+ end
35
+
36
+ def branches
37
+ @branches ||=begin
38
+ branches = [:master]
39
+ branches += [:staging] if @git.has_branch? :staging
40
+ branches
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,99 @@
1
+ module ProjectReleaser
2
+ module Project
3
+ class Repository
4
+ class RepositoryHasNoBranches < RuntimeError; end;
5
+ class RepositoryNotFound < RuntimeError; end;
6
+ class MissingBranch < RuntimeError; end;
7
+
8
+ VERSION_PARTS = %I(major minor patch)
9
+ DEFAULT_VERSION = [0, 0, 0]
10
+
11
+ def initialize(repo_path)
12
+ @git = open_repository(repo_path)
13
+ end
14
+
15
+ def current_version
16
+ Hash[VERSION_PARTS.zip versions.last]
17
+ end
18
+
19
+ def pull(branches)
20
+ branches.each do |branch|
21
+ checkout branch
22
+ @git.remotes.each do |remote|
23
+ @git.fetch remote.name #otherwise it wouldnt get new tags...
24
+ @git.pull remote.name, branch
25
+ end
26
+ end
27
+ end
28
+
29
+ def merge(target_branch, source_branch)
30
+ checkout target_branch
31
+ begin
32
+ @git.merge source_branch
33
+ rescue
34
+ Kernel.system 'git mergetool'
35
+ @git.commit 'resolved merge conflict'
36
+ end
37
+ end
38
+
39
+ def push(branch, version_name)
40
+ checkout branch
41
+ @git.add_tag version_name
42
+ @git.remotes.each do |r|
43
+ @git.push r.name, branch
44
+ @git.push r.name, version_name
45
+ end
46
+ end
47
+
48
+ def remotes
49
+ Hash[@git.remotes.map{ |r| [r.name, r.url] }]
50
+ end
51
+
52
+ def current_branch
53
+ raise RepositoryHasNoBranches unless @git.branches.count > 0
54
+
55
+ @git.branches.find { |b| b.current }.name
56
+ end
57
+
58
+ def checkout(branch)
59
+ @git.checkout branch
60
+ rescue Git::GitExecuteError
61
+ raise MissingBranch, "Branch '#{branch}' is missing"
62
+ end
63
+
64
+ def fetch_tags
65
+ @git.remotes.each { |r| @git.fetch(r.name, :tags => true) }
66
+ end
67
+
68
+ def has_branch?(branch_name)
69
+ @git.branches.map(&:name).include? branch_name.to_s
70
+ end
71
+
72
+ def returning_to_current_branch(&block)
73
+ branch = current_branch
74
+ yield self
75
+ checkout branch
76
+ end
77
+
78
+ private
79
+
80
+ def versions
81
+ tags = @git.tags
82
+ return [DEFAULT_VERSION] if tags.empty?
83
+
84
+ tags
85
+ .map(&:name)
86
+ .select{ |n| n.start_with? 'v' }
87
+ .map{ |n| n.sub('v', '').split('.').map(&:to_i) }
88
+ .map{ |a| a.fill(0, a.size..2) }
89
+ .sort
90
+ end
91
+
92
+ def open_repository(repo_path)
93
+ ::Git.open repo_path
94
+ rescue ArgumentError
95
+ raise RepositoryNotFound
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,18 @@
1
+ module ProjectReleaser
2
+ module Project
3
+ class Updater
4
+ include Logger::Loggable
5
+
6
+ def initialize(git)
7
+ @git = git
8
+ end
9
+
10
+ def update
11
+ logger.info 'updating local release and develop branches'
12
+ @git.returning_to_current_branch do |git|
13
+ git.pull [:master, :staging]
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,39 @@
1
+ module ProjectReleaser
2
+ module Project
3
+ extend Logger::Loggable
4
+ extend self
5
+
6
+ def release(version_type)
7
+ version = next_version version_type
8
+ logger.info "releasing project '#{name}' `#{current_version}` -> `#{version}`"
9
+ releaser = Releaser.new git
10
+ releaser.release version
11
+ end
12
+
13
+ def update
14
+ updater = Updater.new git
15
+ updater.update
16
+ end
17
+
18
+ def name
19
+ info.name
20
+ end
21
+
22
+ def current_version
23
+ info.current_version
24
+ end
25
+
26
+ def next_version(version_type)
27
+ info.next_version version_type
28
+ end
29
+
30
+ private
31
+ def info
32
+ Info.new(git)
33
+ end
34
+
35
+ def git
36
+ @git ||= Repository.new Dir.pwd
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,3 @@
1
+ module ProjectReleaser
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,24 @@
1
+ require 'git'
2
+ require 'ostruct'
3
+ require 'project_releaser/logger/loggable'
4
+ require 'project_releaser/logger/console'
5
+ require 'project_releaser/logger/colored_console'
6
+ require 'project_releaser/logger/null'
7
+ require 'project_releaser/project/repository'
8
+ require 'project_releaser/project/releaser'
9
+ require 'project_releaser/project/updater'
10
+ require 'project_releaser/project/info'
11
+ require 'project_releaser/project'
12
+ require 'project_releaser/cli'
13
+
14
+ module ProjectReleaser
15
+ extend self
16
+
17
+ def configure(&block)
18
+ configuration.tap(&block)
19
+ end
20
+
21
+ def configuration
22
+ @configuration ||= OpenStruct.new
23
+ end
24
+ end
@@ -0,0 +1,29 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'project_releaser/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "project_releaser"
8
+ spec.version = ProjectReleaser::VERSION
9
+ spec.authors = ["kagux"]
10
+ spec.email = ["kaguxmail@gmail.com"]
11
+ spec.description = 'Manage project release routines'
12
+ spec.summary = 'Make project release painless'
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rake"
23
+ spec.add_development_dependency "rspec"
24
+ spec.add_development_dependency "pry"
25
+
26
+ spec.add_runtime_dependency "git"
27
+ spec.add_runtime_dependency "commander"
28
+ spec.add_runtime_dependency "colorize"
29
+ end
@@ -0,0 +1,82 @@
1
+ require 'spec_helper'
2
+ require 'json'
3
+
4
+ describe ProjectReleaser::Project::Info do
5
+
6
+ let(:repository) { double 'git' }
7
+ let(:subject) { ProjectReleaser::Project::Info.new repository }
8
+
9
+ describe '#name' do
10
+ it 'gets name from last remote url' do
11
+ remotes = {
12
+ :origin => 'git@git.example.com/hello.git',
13
+ :upstream => 'git@git.example.com/goodbye.git'
14
+ }
15
+ allow(repository).to receive(:remotes).and_return(remotes)
16
+
17
+ expect(subject.name).to eq 'goodbye'
18
+ end
19
+
20
+ it 'returns unknown when there are no remotes' do
21
+ remotes = {}
22
+ allow(repository).to receive(:remotes).and_return(remotes)
23
+
24
+ expect(subject.name).to eq 'unknown'
25
+ end
26
+ end
27
+
28
+ describe '#current_version' do
29
+ it 'fetches remotes and returns current version' do
30
+ allow(repository).to receive(:current_version).and_return({ :major => 2, :minor => 3, :patch => 3 })
31
+
32
+ allow(repository).to receive(:fetch_tags) do
33
+ allow(repository).to receive(:current_version).and_return({ :major => 2, :minor => 3, :patch => 4 })
34
+ end
35
+
36
+ expect(subject.current_version).to eq 'v2.3.4'
37
+ end
38
+ end
39
+
40
+ describe '#next_version' do
41
+
42
+ before :each do
43
+ allow(repository).to receive(:current_version).and_return({ :major => 2, :minor => 3, :patch => 4 })
44
+ end
45
+
46
+ it 'returns next patch version' do
47
+ expect(subject.next_version :patch).to eq 'v2.3.5'
48
+ end
49
+
50
+ it 'returns next minor version' do
51
+ expect(subject.next_version :minor).to eq 'v2.4.0'
52
+ end
53
+
54
+ it 'returns next major version' do
55
+ expect(subject.next_version :major).to eq 'v3.0.0'
56
+ end
57
+
58
+ it 'returns next patch version by default' do
59
+ expect(subject.next_version).to eq 'v2.3.5'
60
+ end
61
+
62
+ it 'returns next patch version for nil version_type' do
63
+ expect(subject.next_version nil).to eq 'v2.3.5'
64
+ end
65
+
66
+ it 'accepts version type as string' do
67
+ expect(subject.next_version 'major').to eq 'v3.0.0'
68
+ end
69
+
70
+ it 'returns version_type if it is exact version number' do
71
+ expect(subject.next_version 'v7.10.5').to eq 'v7.10.5'
72
+ end
73
+
74
+ it 'returns version_type prefixed with v if it is exact version number' do
75
+ expect(subject.next_version '7.10.5').to eq 'v7.10.5'
76
+ end
77
+
78
+ it 'raises error when version is invalid' do
79
+ expect{subject.next_version 'death star'}.to raise_error
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+
3
+ describe ProjectReleaser::Project::Releaser do
4
+ let(:repository) { double 'repository' }
5
+ let(:yielded_repository) { double 'repository' }
6
+ let(:subject) { ProjectReleaser::Project::Releaser.new repository }
7
+
8
+ before :each do
9
+ allow(repository).to receive(:returning_to_current_branch).and_yield(yielded_repository)
10
+ end
11
+
12
+ it 'releases project with provided version name' do
13
+ allow(yielded_repository).to receive(:has_branch?).with(:staging).and_return(true)
14
+ expect(yielded_repository).to receive(:pull).with([:master, :staging]).ordered
15
+ expect(yielded_repository).to receive(:merge).with(:master, :staging).ordered
16
+ expect(yielded_repository).to receive(:push).with(:master, 'v2.3.5').ordered
17
+
18
+ subject.release 'v2.3.5'
19
+ end
20
+
21
+ it 'doesnt pull and skips merging when there is no staging branch' do
22
+ allow(yielded_repository).to receive(:has_branch?).with(:staging).and_return(false)
23
+ expect(yielded_repository).to receive(:pull).with([:master]).ordered
24
+ expect(yielded_repository).not_to receive(:merge)
25
+ expect(yielded_repository).to receive(:push).with(:master, 'v1.2.4').ordered
26
+
27
+ subject.release 'v1.2.4'
28
+ end
29
+ end
@@ -0,0 +1,224 @@
1
+ require 'spec_helper'
2
+
3
+ describe ProjectReleaser::Project::Repository do
4
+ let(:dir) { '/path/to/repository' }
5
+ let(:subject) { ProjectReleaser::Project::Repository.new dir }
6
+ let(:git) { double 'git lib' }
7
+
8
+ it 'raises exception if directory has no git repository' do
9
+ allow(Git).to receive(:open).with(dir).and_raise(ArgumentError)
10
+ expect{subject}.to raise_error ProjectReleaser::Project::Repository::RepositoryNotFound
11
+ end
12
+
13
+ context 'when dir has repository' do
14
+ before :each do
15
+ allow(Git).to receive(:open).with(dir).and_return(git)
16
+ end
17
+
18
+
19
+ describe '#current_version' do
20
+ it 'takes version from last tag' do
21
+ tag_1 = double 'tag', :name => 'v1.2.3'
22
+ tag_2 = double 'tag', :name => 'v1.2.4'
23
+ allow(git).to receive(:tags).and_return([tag_1, tag_2])
24
+ expect(subject.current_version).to eq :major => 1, :minor => 2, :patch => 4
25
+ end
26
+
27
+ it 'ignores tags that dont start with v' do
28
+ tag_1 = double 'tag', :name => 'v1.2.3'
29
+ tag_2 = double 'tag', :name => '1.2.4'
30
+ allow(git).to receive(:tags).and_return([tag_1, tag_2])
31
+ expect(subject.current_version).to eq :major => 1, :minor => 2, :patch => 3
32
+ end
33
+
34
+ it 'recognizes partial versions' do
35
+ tag = double 'tag', :name => 'v1.2'
36
+ allow(git).to receive(:tags).and_return([tag])
37
+ expect(subject.current_version).to eq :major => 1, :minor => 2, :patch => 0
38
+ end
39
+
40
+ it 'sorts tags by patch version' do
41
+ tag_1 = double 'tag', :name => 'v1.2.10'
42
+ tag_2 = double 'tag', :name => 'v1.2.2'
43
+ allow(git).to receive(:tags).and_return([tag_1, tag_2])
44
+ expect(subject.current_version).to eq :major => 1, :minor => 2, :patch => 10
45
+ end
46
+
47
+ it 'sorts tags by minor version' do
48
+ tag_1 = double 'tag', :name => 'v1.10.2'
49
+ tag_2 = double 'tag', :name => 'v1.2.2'
50
+ allow(git).to receive(:tags).and_return([tag_1, tag_2])
51
+ expect(subject.current_version).to eq :major => 1, :minor => 10, :patch => 2
52
+ end
53
+
54
+ it 'sorts tags by patch version' do
55
+ tag_1 = double 'tag', :name => 'v0.2.2'
56
+ tag_2 = double 'tag', :name => 'v1.2.2'
57
+ allow(git).to receive(:tags).and_return([tag_1, tag_2])
58
+ expect(subject.current_version).to eq :major => 1, :minor => 2, :patch => 2
59
+ end
60
+
61
+ it 'returns default version when there are none' do
62
+ allow(git).to receive(:tags).and_return([])
63
+ expect(subject.current_version).to eq :major => 0, :minor => 0, :patch => 0
64
+ end
65
+ end
66
+
67
+
68
+ describe '#pull' do
69
+ it 'updates provided branches from remote urls' do
70
+ remote_1 = double 'remote url', :name => 'remote_1'
71
+ remote_2 = double 'remote url', :name => 'remote_2'
72
+
73
+ allow(git).to receive(:remotes).and_return([remote_1, remote_2])
74
+
75
+ expect(git).to receive(:checkout).with(:branch_1).ordered
76
+ expect(git).to receive(:fetch).with('remote_1').ordered
77
+ expect(git).to receive(:pull).with('remote_1', :branch_1).ordered
78
+ expect(git).to receive(:fetch).with('remote_2').ordered
79
+ expect(git).to receive(:pull).with('remote_2', :branch_1).ordered
80
+
81
+ expect(git).to receive(:checkout).with(:branch_2).ordered
82
+ expect(git).to receive(:fetch).with('remote_1').ordered
83
+ expect(git).to receive(:pull).with('remote_1', :branch_2).ordered
84
+ expect(git).to receive(:fetch).with('remote_2').ordered
85
+ expect(git).to receive(:pull).with('remote_2', :branch_2).ordered
86
+
87
+ subject.pull([:branch_1, :branch_2])
88
+ end
89
+ end
90
+
91
+
92
+ describe '#merge' do
93
+ it 'merges two branches' do
94
+ expect(git).to receive(:checkout).with(:branch_1).ordered
95
+ expect(git).to receive(:merge).with(:branch_2).ordered
96
+
97
+ subject.merge :branch_1, :branch_2
98
+ end
99
+
100
+ it 'uses mergetool to solve conflicts' do
101
+ allow(git).to receive(:checkout).with(:branch_1)
102
+ allow(git).to receive(:merge).with(:branch_2).and_throw Git::GitExecuteError.new
103
+ expect(Kernel).to receive(:system).with('git mergetool').ordered
104
+ expect(git).to receive(:commit).with('resolved merge conflict')
105
+
106
+ subject.merge :branch_1, :branch_2
107
+ end
108
+ end
109
+
110
+
111
+ describe '#push' do
112
+ it 'tags with provided version and pushes to all remotes urls' do
113
+ remote = double 'remote url', :name => 'some_origin'
114
+ allow(git).to receive(:remotes).and_return([remote])
115
+
116
+ expect(git).to receive(:checkout).with(:branch).ordered
117
+ expect(git).to receive(:add_tag).with('v1.0.0')
118
+ expect(git).to receive(:push).with('some_origin', :branch).ordered
119
+ expect(git).to receive(:push).with('some_origin', 'v1.0.0').ordered
120
+
121
+ subject.push :branch, 'v1.0.0'
122
+ end
123
+ end
124
+
125
+ describe '#remotes' do
126
+ it 'returns hash with remote names and urls' do
127
+ remote_1 = double 'remote url', :name => 'remote_1', :url => 'url_1'
128
+ remote_2 = double 'remote url', :name => 'remote_2', :url => 'url_2'
129
+
130
+ allow(git).to receive(:remotes).and_return([remote_1, remote_2])
131
+
132
+ expect(subject.remotes).to eq 'remote_1' => 'url_1', 'remote_2' => 'url_2'
133
+ end
134
+ end
135
+
136
+
137
+ describe '#current_branch' do
138
+ it 'returns current repository branch name' do
139
+ branch_1 = double 'git branch', :name => 'my_feature 1', :current => false
140
+ branch_2 = double 'git branch', :name => 'my_feature 2', :current => true
141
+ branch_3 = double 'git branch', :name => 'my_feature 3', :current => false
142
+ allow(git).to receive(:branches).and_return([branch_1, branch_2, branch_3])
143
+
144
+ expect(subject.current_branch).to eq 'my_feature 2'
145
+ end
146
+
147
+ it 'raises exception of repository has no branches' do
148
+ allow(git).to receive(:branches).and_return([])
149
+
150
+ expect{subject.current_branch}.to raise_error ProjectReleaser::Project::Repository::RepositoryHasNoBranches
151
+ end
152
+ end
153
+
154
+ describe '#checkout' do
155
+ it 'checks out to branch by name' do
156
+ expect(git).to receive(:checkout).with(:branch)
157
+
158
+ subject.checkout :branch
159
+ end
160
+
161
+ it 'raises exception if the branch is missing' do
162
+ allow(git).to receive(:checkout).with(:branch).and_raise(Git::GitExecuteError)
163
+ expect{subject.checkout :branch}.to raise_error ProjectReleaser::Project::Repository::MissingBranch
164
+ end
165
+ end
166
+
167
+ describe '#fetch_tags' do
168
+ it 'fetches tags from all remotes' do
169
+ remote_1 = double 'remote url', :name => 'remote_1'
170
+ remote_2 = double 'remote url', :name => 'remote_2'
171
+ allow(git).to receive(:remotes).and_return([remote_1, remote_2])
172
+
173
+ expect(git).to receive(:fetch).with('remote_1', :tags => true)
174
+ expect(git).to receive(:fetch).with('remote_2', :tags => true)
175
+
176
+ subject.fetch_tags
177
+ end
178
+ end
179
+
180
+ describe '#has_branch' do
181
+ let(:branch) {double 'git branch', :name => 'the_branch'}
182
+ before :each do
183
+ allow(git).to receive(:branches).and_return([branch])
184
+ end
185
+
186
+ it 'returns true if branch exists' do
187
+ expect(subject.has_branch?(:the_branch)).to be_truthy
188
+ end
189
+
190
+ it 'returns false if branch exists' do
191
+ expect(subject.has_branch?(:another_branch)).to be_falsy
192
+ end
193
+ end
194
+
195
+ describe '#returning_to_current_branch' do
196
+ let(:git) { double('git').as_null_object }
197
+ let(:tmp_branch) { double 'git branch', :name => :tmp_branch, :current => false }
198
+ let(:current_branch) { double 'git branch', :name => :current_branch, :current => true }
199
+
200
+ before :each do
201
+ allow(Git).to receive(:open).with(dir).and_return(git)
202
+ allow(git).to receive(:branches).and_return([current_branch, tmp_branch])
203
+ end
204
+
205
+ it 'yields self' do
206
+ subject.returning_to_current_branch do |obj|
207
+ expect(obj).to be subject
208
+ end
209
+ end
210
+
211
+ it 'checks out current branch after yielding block' do
212
+ subject.returning_to_current_branch do
213
+ #change current branch to something else
214
+ allow(current_branch).to receive(:current).and_return(false)
215
+ allow(tmp_branch).to receive(:current).and_return(true)
216
+
217
+ expect(git).to_not have_received(:checkout)
218
+ end
219
+
220
+ expect(git).to have_received(:checkout).with(:current_branch)
221
+ end
222
+ end
223
+ end
224
+ end
@@ -0,0 +1,14 @@
1
+ require 'spec_helper'
2
+
3
+ describe ProjectReleaser::Project::Updater do
4
+ let(:repository) { double 'repository' }
5
+ let(:yielded_repository) { double 'repository' }
6
+ let(:subject) { ProjectReleaser::Project::Updater.new repository }
7
+
8
+ it 'updates master and staging branches from all remotes' do
9
+ allow(repository).to receive(:returning_to_current_branch).and_yield(yielded_repository)
10
+ expect(yielded_repository).to receive(:pull).with([:master, :staging])
11
+
12
+ subject.update
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ require 'spec_helper'
2
+ describe ProjectReleaser do
3
+ let(:subject) {ProjectReleaser}
4
+
5
+ it 'is configurable' do
6
+ subject.configure do |c|
7
+ c.key_a = :value_a
8
+ c.key_b = :value_b
9
+ end
10
+
11
+ expect(subject.configuration.key_a).to eq :value_a
12
+ expect(subject.configuration.key_b).to eq :value_b
13
+ end
14
+ end
@@ -0,0 +1,6 @@
1
+ require 'git'
2
+ require 'project_releaser'
3
+
4
+ ProjectReleaser.configure do |config|
5
+ config.logger = ProjectReleaser::Logger::Null.new
6
+ end
data/todo ADDED
@@ -0,0 +1 @@
1
+ configurable branches
File without changes
@@ -0,0 +1,33 @@
1
+ - hosts: localhost
2
+ sudo: yes
3
+ vars:
4
+ ruby_gemset: 'releaser'
5
+ rvm1_rubies: [ 'ruby-2.1.5' ]
6
+ rvm1_gpg_key_server: 'pgp.uni-mainz.de'
7
+ rvm1_rvm_check_for_updates: False
8
+ app_home_path: /vagrant
9
+ mysql_root_password: ''
10
+
11
+ pre_tasks:
12
+ - name: install packages
13
+ apt: name={{item}} state=present
14
+ with_items:
15
+ - git
16
+ - curl
17
+ - htop
18
+ - libyaml-dev
19
+
20
+ tasks:
21
+ - name: prepare application
22
+ shell: bash -lc "rvm use {{rvm1_rubies[0]}}@{{ruby_gemset}} --create && {{item}}"
23
+ sudo: no
24
+ args:
25
+ chdir: "{{app_home_path}}"
26
+ with_items:
27
+ - 'bundle install'
28
+
29
+ roles:
30
+ - rvm_io.rvm1-ruby
31
+ - role: kagux.dev_environment
32
+ dotfiles_repo: https://github.com/kagux/dotfiles.git
33
+ when: host_os_user == 'boris'
@@ -0,0 +1,2 @@
1
+ [localhost]
2
+ localhost ansible_connection=local
@@ -0,0 +1,7 @@
1
+ rvm_io.rvm1-ruby
2
+ ANXS.mysql
3
+ JasonGiedymin.nodejs
4
+ kagux.dev_environment
5
+ kagux.docker
6
+ kagux.docker_compose
7
+ DavidWittman.redis
@@ -0,0 +1,26 @@
1
+ ANSIBLE_PATH="/vagrant/vagrant/ansible"
2
+ USER=$1
3
+
4
+ #temporarly rename gemset lock
5
+ mv /vagrant/.ruby-gemset /vagrant/ruby-gemset.tmp 2>/dev/null
6
+
7
+ #install ansible
8
+ apt-get install -y software-properties-common \
9
+ && apt-add-repository -y ppa:ansible/ansible \
10
+ && apt-get update \
11
+ && apt-get install -y ansible \
12
+ && cd /vagrant/vagrant/ansible \
13
+ && ansible-galaxy install -r requirements.galaxy --force
14
+
15
+ # #create rvm group if it doesn't exist
16
+ getent group rvm || groupadd rvm
17
+ # #add vagrant user to rvm group to have correct permissions
18
+ usermod -a -G rvm vagrant
19
+
20
+ # #run playbook
21
+ sudo -H -u vagrant bash -c "ansible-playbook -i $ANSIBLE_PATH/inventory --extra-vars host_os_user=$USER $ANSIBLE_PATH/app.yml"
22
+
23
+ #revert gemlock changes
24
+ mv /vagrant/ruby-gemset.tmp /vagrant/.ruby-gemset 2>/dev/null
25
+
26
+ exit 0
metadata ADDED
@@ -0,0 +1,183 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: project_releaser
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - kagux
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-06-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.3'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: pry
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: git
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: commander
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: colorize
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ description: Manage project release routines
112
+ email:
113
+ - kaguxmail@gmail.com
114
+ executables:
115
+ - project
116
+ extensions: []
117
+ extra_rdoc_files: []
118
+ files:
119
+ - ".gitignore"
120
+ - ".rspec"
121
+ - Gemfile
122
+ - Gemfile.lock
123
+ - LICENSE.txt
124
+ - README.md
125
+ - Rakefile
126
+ - Vagrantfile
127
+ - bin/project
128
+ - lib/project_releaser.rb
129
+ - lib/project_releaser/cli.rb
130
+ - lib/project_releaser/logger/colored_console.rb
131
+ - lib/project_releaser/logger/console.rb
132
+ - lib/project_releaser/logger/loggable.rb
133
+ - lib/project_releaser/logger/null.rb
134
+ - lib/project_releaser/project.rb
135
+ - lib/project_releaser/project/info.rb
136
+ - lib/project_releaser/project/releaser.rb
137
+ - lib/project_releaser/project/repository.rb
138
+ - lib/project_releaser/project/updater.rb
139
+ - lib/project_releaser/version.rb
140
+ - project_releaser.gemspec
141
+ - spec/project_releaser/project/info_spec.rb
142
+ - spec/project_releaser/project/releaser_spec.rb
143
+ - spec/project_releaser/project/repository_spec.rb
144
+ - spec/project_releaser/project/updater_spec.rb
145
+ - spec/project_releaser_spec.rb
146
+ - spec/spec_helper.rb
147
+ - todo
148
+ - vagrant/.ruby-version
149
+ - vagrant/ansible/app.yml
150
+ - vagrant/ansible/inventory
151
+ - vagrant/ansible/requirements.galaxy
152
+ - vagrant/provision.sh
153
+ homepage: ''
154
+ licenses:
155
+ - MIT
156
+ metadata: {}
157
+ post_install_message:
158
+ rdoc_options: []
159
+ require_paths:
160
+ - lib
161
+ required_ruby_version: !ruby/object:Gem::Requirement
162
+ requirements:
163
+ - - ">="
164
+ - !ruby/object:Gem::Version
165
+ version: '0'
166
+ required_rubygems_version: !ruby/object:Gem::Requirement
167
+ requirements:
168
+ - - ">="
169
+ - !ruby/object:Gem::Version
170
+ version: '0'
171
+ requirements: []
172
+ rubyforge_project:
173
+ rubygems_version: 2.4.7
174
+ signing_key:
175
+ specification_version: 4
176
+ summary: Make project release painless
177
+ test_files:
178
+ - spec/project_releaser/project/info_spec.rb
179
+ - spec/project_releaser/project/releaser_spec.rb
180
+ - spec/project_releaser/project/repository_spec.rb
181
+ - spec/project_releaser/project/updater_spec.rb
182
+ - spec/project_releaser_spec.rb
183
+ - spec/spec_helper.rb