vershunt 2.0.4

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
+ 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