vershunt 2.0.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA512:
3
+ data.tar.gz: 489aaa7a9417ff0c038fc4bc149ecfc1dd465cc18ea3ecc99384f4e63fe51b70ee7bf918937cd7a569e1715edfc000d4ee88392e67d37bd772b4e255291348b8
4
+ metadata.gz: 41ad772239c464ba17b89dd070a1b8df7801edcd93511ac3db554f6a5b84ac08b13e57082542f8624041e4c4951da6c33f944ca992a2c270c9c80763108fa2eb
5
+ SHA1:
6
+ data.tar.gz: d86f32da8656675a8ad473d55466d4ef551ad276
7
+ metadata.gz: a60005aa2a2a9bab6c5c31801ce8379ce1a43aac
data/bin/vershunt ADDED
@@ -0,0 +1,19 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ INSTALL_DIR='/usr/lib/vershunt'
4
+ INSTALL_BIN='/usr/bin/vershunt'
5
+
6
+ lib_dir =
7
+ if __FILE__ == INSTALL_BIN
8
+ # smells like debian, rely on fixed installation locations
9
+ INSTALL_DIR + '/lib'
10
+ else
11
+ # local dev copy, or installed via rubygems
12
+ require 'rubygems'
13
+ File.expand_path(File.join(File.dirname(__FILE__), "../lib"))
14
+ end
15
+
16
+ $LOAD_PATH.unshift(lib_dir)
17
+
18
+ require 'msp_release'
19
+ MSPRelease::CLI.run(ARGV)
@@ -0,0 +1,153 @@
1
+ require 'yaml'
2
+
3
+ module MSPRelease
4
+
5
+ require 'climate'
6
+ require 'msp_release/exec'
7
+
8
+ include Exec::Helpers
9
+
10
+ module Helpers
11
+
12
+ PROJECT_FILE = ".msp_project"
13
+
14
+ # Slush bucket for stuff :)
15
+
16
+ def msp_version
17
+ project.version
18
+ end
19
+
20
+ def git_version
21
+ (m = /release-(.+)/.match(git.cur_branch)) && m[1]
22
+ end
23
+
24
+ def time; @time ||= Time.now; end
25
+
26
+ def timestamp
27
+ @timestamp ||= time.strftime("%Y%m%d%H%M%S")
28
+ end
29
+
30
+ def time_rfc
31
+ offset = time.gmt_offset / (60 * 60)
32
+ gmt_offset = "#{offset < 0 ? '-' : '+'}#{offset.abs.to_s.rjust(2, "0")}00"
33
+ time.strftime("%a, %d %b %Y %H:%M:%S #{gmt_offset}")
34
+ end
35
+
36
+ def author
37
+ @author ||=
38
+ begin
39
+ name, email = ['name', 'email'].map {|f| `git config --get user.#{f}`.strip}
40
+ Author.new(name, email)
41
+ end
42
+ end
43
+
44
+ def changelog
45
+ @changelog ||= begin
46
+ project.respond_to?(:changelog) && project.changelog
47
+ end
48
+ end
49
+
50
+ def on_release_branch?
51
+ !!/release/.match(git.cur_branch)
52
+ end
53
+
54
+ def data
55
+ @data ||= {}
56
+ end
57
+
58
+ def data=(data_hash)
59
+ @data = data_hash
60
+ end
61
+
62
+ def data_exists?
63
+ File.exists?(DATAFILE)
64
+ end
65
+
66
+ def load_data
67
+ @data = File.open(DATAFILE, 'r') {|f| YAML.load(f) }
68
+ end
69
+
70
+ def save_data
71
+ File.open(DATAFILE, 'w') {|f| YAML.dump(@data, f) }
72
+ end
73
+
74
+ def remove_data
75
+ File.delete(DATAFILE) if File.exists?(DATAFILE)
76
+ end
77
+
78
+ def fail_if_modified_wc
79
+ annoying_files = git.modified_files + git.added_files
80
+ if annoying_files.length > 0
81
+ $stderr.puts("You have modified files in your working copy, and that just won't do")
82
+ annoying_files.each {|f|$stderr.puts(" " + f)}
83
+ exit 1
84
+ end
85
+ end
86
+
87
+ def fail_if_push_pending
88
+ if data_exists?
89
+ $stderr.puts("You have a release commit pending to be pushed")
90
+ $stderr.puts("Please push, or reset the operation")
91
+ exit 1
92
+ end
93
+ end
94
+ end
95
+
96
+ include Helpers
97
+
98
+ require 'msp_release/log'
99
+ require 'msp_release/debian'
100
+ require 'msp_release/git'
101
+ require 'msp_release/options'
102
+ require 'msp_release/project'
103
+ require 'msp_release/build'
104
+ require 'msp_release/make_branch'
105
+ require 'msp_release/cli'
106
+
107
+ MSP_VERSION_FILE = "lib/msp/version.rb"
108
+ DATAFILE = ".msp_release"
109
+
110
+ LOG = Log.new
111
+
112
+ VERSION_SEGMENTS = [:major, :minor, :bugfix]
113
+ Version = Struct.new(*VERSION_SEGMENTS)
114
+ Version.module_eval do
115
+
116
+ def format(opts={})
117
+ opts[:without_bugfix] ? "#{major}.#{minor}" : "#{major}.#{minor}.#{bugfix}"
118
+ end
119
+
120
+ alias :to_s :format
121
+
122
+ def self.from_string(str)
123
+ match = /([0-9]+)\.([0-9]+)\.([0-9]+)/.match(str)
124
+ match && new(*(1..3).map{|i|match[i]})
125
+ end
126
+
127
+ def bump(segment)
128
+
129
+ raise ArgumentError, "no such segment: #{segment}" unless VERSION_SEGMENTS.include?(segment)
130
+
131
+ reset = false
132
+ new_segments = VERSION_SEGMENTS.map do |cur_seg|
133
+ part = self.send(cur_seg)
134
+ if cur_seg == segment
135
+ reset = true
136
+ part.to_i + 1
137
+ else
138
+ reset ? 0 : part
139
+ end.to_s
140
+ end
141
+
142
+ "new_segments: #{new_segments}"
143
+
144
+ self.class.new(*new_segments)
145
+ end
146
+ end
147
+
148
+ Author = Struct.new(:name, :email)
149
+
150
+ extend self
151
+
152
+ end
153
+
@@ -0,0 +1,64 @@
1
+ module MSPRelease
2
+ class Build
3
+
4
+ class NoChangesFileError < StandardError ; end
5
+
6
+ include Exec::Helpers
7
+
8
+ def initialize(basedir, project, options={})
9
+ @basedir = basedir
10
+ @project = project
11
+ @options = options
12
+
13
+ @sign = options.fetch(:sign, true)
14
+ end
15
+
16
+ def perform_from_cli!
17
+ LOG.debug("Building package...")
18
+
19
+ result =
20
+ begin
21
+ self.perform!
22
+ rescue Exec::UnexpectedExitStatus => e
23
+ raise CLI::Exit, "build failed:\n#{e.stderr}"
24
+ rescue Build::NoChangesFileError => e
25
+ raise CLI::Exit, "Unable to find changes file with version: " +
26
+ "#{e.message}\nAvailable: \n" +
27
+ self.available_changes_files.map { |f| " #{f}" }.join("\n")
28
+ end
29
+
30
+ result.tap do
31
+ LOG.debug("Package built: #{result.package}")
32
+ end
33
+ end
34
+
35
+ def perform!
36
+ dir = File.expand_path(@basedir)
37
+ raise "directory does not exist: #{dir}" unless
38
+ File.directory?(dir)
39
+
40
+ e = Exec.new(:name => 'build', :quiet => false, :status => :any)
41
+ Dir.chdir(@project.dir) do
42
+ e.exec(build_command)
43
+ end
44
+
45
+ if e.last_exitstatus != 0
46
+ LOG.warn("Warning: #{build_command} exited with #{e.last_exitstatus}")
47
+ end
48
+
49
+ @project.build_result(output_directory)
50
+ end
51
+
52
+ def output_directory
53
+ File.expand_path(@project.config[:deb_output_directory] ||
54
+ File.join(@basedir, '..'))
55
+ end
56
+
57
+ private
58
+
59
+ def build_command
60
+ @project.build_command
61
+ end
62
+
63
+ end
64
+ end
@@ -0,0 +1,90 @@
1
+ module MSPRelease
2
+ module CLI
3
+
4
+ # Base class for command line operations
5
+ class Command < Climate::Command
6
+ include Exec::Helpers
7
+ end
8
+
9
+ # alias this class for shorter lines
10
+ Exit = Climate::ExitException
11
+
12
+ # root of the command hierarchy
13
+ class Root < Climate::Command('vershunt')
14
+
15
+ description """
16
+ Manipulate your git repository by creating commits and performing
17
+ branch management to create a consistent log of commits to be used
18
+ as part of a repeatable build system, as well as encouraging
19
+ semantic versioning (see http://semver.org).
20
+
21
+ Projects must include a .msp_project, which is a yaml file that must at least
22
+ not be empty. If the project file has a ruby_version_file key, then this
23
+ file will be considered as well as the debian changelog when updating version
24
+ information.
25
+ """
26
+
27
+ end
28
+
29
+ # Commands that require a git working copy can include this module
30
+ module WorkingCopyCommand
31
+
32
+ include Helpers
33
+
34
+ attr_accessor :project, :git
35
+
36
+ def initialize(options, leftovers)
37
+ super
38
+
39
+ if File.exists?(PROJECT_FILE)
40
+ @project = MSPRelease::Project.new_from_project_file(PROJECT_FILE)
41
+ else
42
+ raise Climate::ExitException.
43
+ new("No #{PROJECT_FILE} present in current directory")
44
+ end
45
+
46
+ @git = Git.new(@project, @options)
47
+ end
48
+
49
+ end
50
+
51
+ # hardcoded list of commands
52
+ COMMANDS = ['help', 'new', 'push', 'branch', 'status', 'reset', 'bump', 'checkout', 'build']
53
+
54
+ # These are available on the CLI module
55
+ module ClassMethods
56
+
57
+ attr_reader :commands
58
+
59
+ def run(args)
60
+ init_commands
61
+
62
+ Climate.with_standard_exception_handling do
63
+ begin
64
+ Root.run(args)
65
+ rescue Exec::UnexpectedExitStatus => e
66
+ $stderr.puts(e.message)
67
+ $stderr.puts(e.stderr)
68
+ exit 1
69
+ end
70
+ end
71
+ end
72
+
73
+ def init_commands
74
+ @commands = {}
75
+ COMMANDS.each do |name|
76
+ require "msp_release/cli/#{name}"
77
+ camel_name =
78
+ name.split(/[^a-z0-9]/i).map{|w| w.capitalize}.join
79
+
80
+ command = MSPRelease::CLI.const_get(camel_name)
81
+ @commands[name] = command
82
+ command.set_name(name)
83
+ command.subcommand_of(Root)
84
+ end
85
+ end
86
+ end
87
+
88
+ self.extend(ClassMethods)
89
+ end
90
+ end
@@ -0,0 +1,97 @@
1
+ module MSPRelease
2
+ class CLI::Branch < CLI::Command
3
+ include CLI::WorkingCopyCommand
4
+
5
+ description """
6
+ Create and switch to a release branch for the version on HEAD
7
+
8
+ The release branch will be named after the project version returned by
9
+ `vershunt status`, excluding the bugfix version. If the version is
10
+ 1.2.3, a branch of release-1.2 will be created.
11
+
12
+ The minor version on master is bumped after the branch is created, although this can be disabled.
13
+ """
14
+
15
+ opt :allow_non_master_branch, "Allow release branch to be created " +
16
+ "even if you are not on master. Normally you would not want to do" +
17
+ " this, so this is here to prevent branches from mistakenly being " +
18
+ "created from branches other than master",
19
+ {
20
+ :short => 'a',
21
+ :default => false
22
+ }
23
+
24
+
25
+ opt :no_bump_master, "Do not bump the minor version of master " +
26
+ "as part of creating the release branch. Typically after creating a " +
27
+ "release branch, the minor version being stabilised now lives on the " +
28
+ "branch and master is now a new version",
29
+ {
30
+ :short => 'n',
31
+ :default => false
32
+ }
33
+
34
+ def run
35
+ fail_if_push_pending
36
+
37
+ # take the version before we do any bumping
38
+ version = project.version
39
+
40
+ branch_from =
41
+ if git.on_master?
42
+ bump_and_push_master
43
+ else
44
+ check_branching_ok!
45
+ end
46
+
47
+ branch_name = project.branch_name(version)
48
+
49
+ begin
50
+ MSPRelease::MakeBranch.new(git, branch_name, :start_point => branch_from).
51
+ perform!
52
+ rescue MSPRelease::MakeBranch::BranchExistsError => e
53
+ raise CLI::Exit, "A branch already exists for #{version}"
54
+ end
55
+
56
+ $stdout.puts("Switched to release branch '#{branch_name}'")
57
+ end
58
+
59
+ def check_branching_ok!
60
+ if options[:allow_non_master_branch]
61
+ $stderr.puts("Creating a non-master release branch, --allow-non-master-branch supplied")
62
+ "HEAD@{0}"
63
+ else
64
+ raise CLI::Exit, "You must be on master to create " +
65
+ "release branches, or pass --allow-non-master-branch"
66
+ end
67
+ end
68
+
69
+ def bump_and_push_master
70
+
71
+ return "HEAD@{0}" if options[:no_bump_master]
72
+
73
+ # don't let this happen with a dirty working copy, because we reset the
74
+ # master branch, which will kill all your changes
75
+ fail_if_modified_wc
76
+
77
+ new_version, *changed_files = project.bump_version('minor')
78
+ exec "git add -- #{changed_files.join(' ')}"
79
+
80
+ # FIXME dry this part up, perhaps using a bump operation class
81
+ exec "git commit -m 'BUMPED VERSION TO #{new_version}'"
82
+
83
+ begin
84
+ $stdout.puts "Bumping master to #{new_version}, pushing to origin..."
85
+ exec "git push origin master"
86
+ rescue
87
+ $stderr.puts "error pushing bump commit to master, undoing bump..."
88
+ exec "git reset --hard HEAD@{1}"
89
+ raise CLI::Exit, 'could not push bump commit to master, if you do ' +
90
+ 'not want to bump the minor version of master, try again with ' +
91
+ '--no-bump-master'
92
+ end
93
+
94
+ "HEAD@{1}"
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,99 @@
1
+ module MSPRelease
2
+ class CLI::Build < CLI::Command
3
+ include Debian::Versions
4
+
5
+ # When cloning repositories, limit to this many commits from each head
6
+ CLONE_DEPTH = 5
7
+
8
+ description """Build debian packages suitable for deployment fresh from
9
+ source control using a build commit or a development version.
10
+
11
+ When no BRANCH_NAME is given, or that branch is not a release branch, the
12
+ latest commit from that branch is checked out and the changelog version is
13
+ adjusted to show this is a development build.
14
+
15
+ If BRANCH_NAME denotes a release branch (i.e release-1.1) then the latest
16
+ /release/ commit is checked out, even if there are commits after it.
17
+ The changelog remains unaltered in this case - the source tree should have
18
+ the correct version information in it.
19
+ """
20
+
21
+ arg :git_url, "URL used to clone the git repository"
22
+
23
+ arg :branch_name, "Name of a branch to build from, defaults to ${default}",
24
+ :default => "master"
25
+
26
+ opt :sign, "Pass options to dpkg-buildpackage to tell it whether or not to sign the build products",
27
+ {
28
+ :short => 'S',
29
+ :default => false
30
+ }
31
+
32
+ opt :shallow, "Only perform a shallow checkout to a depth of five" +
33
+ "commits from each head. See git documentation for more details",
34
+ {
35
+ :short => 's',
36
+ :default => true
37
+ }
38
+
39
+ opt :distribution, "Specify the debian distribution to put in the " +
40
+ "changelog when checking out a development version",
41
+ {
42
+ :short => 'd',
43
+ :long => 'debian-distribution',
44
+ :type => :string
45
+ }
46
+
47
+ opt :verbose, "Print some useful debugging output to stdout",
48
+ {
49
+ :long => 'verbose',
50
+ :short => 'v', :default => false
51
+ }
52
+
53
+ opt :noisy, "Output dpkg-buildpackage output to stderr",
54
+ {
55
+ :short => 'n', :default => false
56
+ }
57
+
58
+ opt :silent, "Do not print out build products to stdout", {
59
+ :default => false
60
+ }
61
+
62
+ def run
63
+ git_url = arguments[:git_url]
64
+ release_spec_arg = arguments[:branch_name]
65
+
66
+ do_build = options[:build]
67
+ tar_it = options[:tar]
68
+ clone_depth = options[:shallow] ? CLONE_DEPTH : nil
69
+
70
+ LOG.silent if options[:silent]
71
+ LOG.verbose if options[:verbose]
72
+ LOG.noisy if options[:noisy]
73
+
74
+ branch_name = release_spec_arg || 'master'
75
+
76
+ shallow_output = clone_depth.nil?? '' : ' (shallow)'
77
+
78
+ options[:shallow_output] = shallow_output
79
+
80
+ # checkout project
81
+ tmp_dir = "vershunt-#{Time.now.to_i}.tmp"
82
+ Git.clone(git_url, {:depth => clone_depth, :out_to => tmp_dir,
83
+ :branch => branch_name, :no_single_branch => true,
84
+ :exec => {:quiet => true}})
85
+
86
+ project = Project.new_from_project_file(tmp_dir + "/" + Helpers::PROJECT_FILE)
87
+
88
+ project.prepare_for_build(branch_name, options)
89
+
90
+ if project.respond_to?(:build)
91
+ project.build(options)
92
+ else
93
+ raise "I don't know how to build this project"
94
+ end
95
+
96
+ end
97
+
98
+ end
99
+ end