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.
@@ -0,0 +1,120 @@
1
+ require 'thread'
2
+ require 'popen4'
3
+
4
+ module MSPRelease
5
+ class Exec
6
+
7
+ class UnexpectedExitStatus < StandardError
8
+ def initialize(expected, actual, cmd, output, stdout, stderr)
9
+ @expected = expected
10
+ @actual = actual
11
+ @command = cmd
12
+ @stdout = stdout
13
+ @stderr = stderr
14
+ super("command '#{command}' exited with #{actual}, expected #{expected}")
15
+ end
16
+
17
+ attr_reader :expect, :actual, :command, :output, :stdout, :stderr
18
+ # this marries up with what popen et al return
19
+ alias :exitstatus :actual
20
+ end
21
+
22
+ module Helpers
23
+ def exec(command, options={})
24
+ # use the options we were constructed with if they exist
25
+ if respond_to?(:options)
26
+ options[:quiet] = !self.options.verbose? unless options.has_key? :quiet
27
+ end
28
+
29
+ if respond_to? :exec_name
30
+ options[:name] = exec_name unless options.has_key? :name
31
+ end
32
+
33
+ MSPRelease::Exec.exec(command, options)
34
+ end
35
+ end
36
+
37
+ def self.exec(command, options={})
38
+ new(options).exec(command, options)
39
+ end
40
+
41
+ attr_reader :name
42
+
43
+ def initialize(options={})
44
+ @name = options.fetch(:name, nil)
45
+ end
46
+
47
+ def last_stdout
48
+ @last_stdout
49
+ end
50
+
51
+ def last_stderr
52
+ @last_stderr
53
+ end
54
+
55
+ def last_output
56
+ @last_output
57
+ end
58
+
59
+ def last_exitstatus
60
+ @last_exitstatus
61
+ end
62
+
63
+ def exec(command, options={})
64
+
65
+ expected = to_expected_array(options.fetch(:status, 0))
66
+
67
+ @last_stdout = ""
68
+ @last_stderr = ""
69
+ @last_output = ""
70
+
71
+ output = options[:output]
72
+
73
+ output_semaphore = Mutex.new
74
+
75
+ start = name.nil? ? '' : "#{name}: "
76
+
77
+ status = POpen4::popen4(command) do |stdout, stderr, stdin, pid|
78
+ t1 = Thread.new do
79
+ stdout.each_line do |line|
80
+ @last_stdout += line
81
+ output_semaphore.synchronize { @last_output += line }
82
+ msg = "#{start}#{line}"
83
+ (output && !options[:quiet]) ? output.puts(msg) : LOG.debug(msg)
84
+ end
85
+ end
86
+
87
+ t2 = Thread.new do
88
+ stderr.each_line do |line|
89
+ @last_stderr += line
90
+ output_semaphore.synchronize { @last_output += line }
91
+ msg = "#{start}#{line}"
92
+ (output && !options[:quiet]) ? output.puts(msg) : LOG.error(msg)
93
+ end
94
+ end
95
+
96
+ t1.join
97
+ t2.join
98
+ end
99
+
100
+ @last_exitstatus = status.exitstatus
101
+
102
+ unless expected.nil? || expected.include?(status.exitstatus)
103
+ raise UnexpectedExitStatus.new(expected, status.exitstatus, command, @last_output, @last_stdout, @last_stderr)
104
+ end
105
+
106
+ @last_stdout
107
+ end
108
+
109
+ private
110
+
111
+ def to_expected_array(value)
112
+ case value
113
+ when :any then nil
114
+ when Array then value
115
+ when Numeric then [value]
116
+ else raise ArgumentError, "#{value} is not an acceptable exit status"
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,145 @@
1
+ class MSPRelease::Git
2
+
3
+ # methods that don't require a local clone
4
+ module ClassMethods
5
+ def version()
6
+ MSPRelease::Exec.exec("git version", :output => StringIO.new)
7
+ end
8
+
9
+ def version_gte(maj, min=0, bug=0)
10
+ match = version.match(/(\d+)\.(\d+)\.(\d+)\..*/)
11
+ match[1].to_i >= maj &&
12
+ match[2].to_i >= min &&
13
+ match[3].to_i >= bug
14
+ end
15
+
16
+ def clone(git_url, options={})
17
+ out_to = options[:out_to]
18
+ exec_options = options[:exec] || {}
19
+ depth_arg = options[:depth].nil?? '' : "--depth #{options[:depth]}"
20
+ branch = options[:branch].nil?? '' : "--branch #{options[:branch]}"
21
+
22
+ heads = StringIO.new
23
+ MSPRelease::Exec.exec("git ls-remote --heads #{git_url}", :output => heads)
24
+
25
+ unless heads.string.match(/#{options[:branch]}/)
26
+ raise MSPRelease::CLI::Exit, "Git pathspec 'origin/#{options[:branch]}' does not exist"
27
+ end
28
+
29
+ single_branch = if version_gte(1, 7, 10)
30
+ options[:no_single_branch] ? '--no-single-branch' :
31
+ options[:single_branch] ? '--single-branch' :
32
+ ''
33
+ end
34
+
35
+ MSPRelease::Exec.
36
+ exec("git clone #{depth_arg} #{single_branch} #{branch} #{git_url} #{out_to.nil?? '' : out_to}", exec_options)
37
+ end
38
+ end
39
+
40
+ class << self
41
+ include ClassMethods
42
+ end
43
+
44
+ class Commit
45
+ include MSPRelease::Exec::Helpers
46
+ attr_reader :message
47
+ attr_reader :author
48
+ attr_reader :hash
49
+
50
+ def initialize(project, attributes)
51
+ @project = project
52
+ attributes.each do |k, v|
53
+ instance_variable_set(:"@#{k}", v)
54
+ end
55
+ end
56
+
57
+ def release_commit?
58
+ !! (release_name || tag)
59
+ end
60
+
61
+ def tag
62
+ tag = exec "git tag --contains #{hash}"
63
+ (m = /^release-([0-9.]*)$/.match(tag)) && m[1]
64
+ end
65
+
66
+ def release_name
67
+ @project.release_name_from_message(message) || tag
68
+ end
69
+ end
70
+
71
+ include MSPRelease::Exec::Helpers
72
+
73
+ def initialize(project, options)
74
+ @project = project
75
+ @options = options
76
+ end
77
+
78
+ def exec_name; 'git'; end
79
+
80
+ attr_reader :options
81
+
82
+ def on_master?
83
+ cur_branch == 'master'
84
+ end
85
+
86
+ def cur_branch
87
+ /^\* (.+)$/.match(exec "git branch")[1]
88
+ end
89
+
90
+ def last_release_tag
91
+ a = exec "git describe --tags --match 'release-*'"
92
+ end
93
+
94
+ def branch_exists?(branch_name)
95
+ # use backticks, we don't want an error
96
+ `git show-branch origin/#{branch_name}`
97
+ $? == 0
98
+ end
99
+
100
+ def create_and_switch(branch_name, options={})
101
+ start_point = options[:start_point]
102
+ exec "git branch #{branch_name} #{start_point}"
103
+ exec "git push origin #{branch_name}"
104
+ exec "git checkout #{branch_name}"
105
+ end
106
+
107
+ def added_files
108
+ status_files("new file")
109
+ end
110
+
111
+ def modified_files
112
+ status_files("modified")
113
+ end
114
+
115
+ def status_files(status)
116
+ output = exec "git status", :status => :any
117
+ pattern = /#{status}: +(.+)$/
118
+ output.split("\n").map {|l| pattern.match(l) }.compact.map{|m|m[1]}
119
+ end
120
+
121
+ def latest_commit(project)
122
+ commit_output =
123
+ exec("git --no-pager log -1 --no-color --full-index --pretty=short").split("\n")
124
+
125
+ commit_pattern = /^commit ([a-z0-9]+)$/
126
+ author_pattern = /^Author: (.+)$/
127
+ message_pattern = /^ (.+)$/
128
+
129
+ hash = commit_output.grep(commit_pattern) {|m| commit_pattern.match(m)[1] }.first
130
+ author = commit_output.grep(author_pattern) {|m| author_pattern.match(m)[1] }.first
131
+ message = commit_output.grep(message_pattern).
132
+ map {|row| message_pattern.match(row)[1] }.
133
+ join(" ")
134
+
135
+ Commit.new(project, {:hash => hash, :author => author, :message => message})
136
+ end
137
+
138
+ def remote_is_ahead?
139
+ raise NotImplementedError
140
+ branch_name = cur_branch
141
+ exec "git fetch"
142
+ exec "git --no-pager log origin/#{branch_name} --no-color --oneline -1".split("")
143
+ end
144
+
145
+ end
@@ -0,0 +1,48 @@
1
+ # simple class for keeping output in one place
2
+ module MSPRelease
3
+ class Log
4
+ @@log_levels = {
5
+ :error => $stderr,
6
+ :warn => $stderr,
7
+ :info => $stdout,
8
+ :debug => StringIO.new,
9
+ :trace => StringIO.new
10
+ }
11
+
12
+ def initialize
13
+ end
14
+
15
+ def verbose
16
+ @@log_levels[:debug] = $stdout
17
+ end
18
+
19
+ def noisy
20
+ @@log_levels[:debug] = $stderr
21
+ end
22
+
23
+ def silent
24
+ @@log_levels.keys.each do |level|
25
+ @@log_levels[level] = StringIO.new
26
+ end
27
+ end
28
+
29
+ @@log_levels.keys.each do |level|
30
+ define_method level do |msg|
31
+ @@log_levels[level].puts(msg)
32
+ end
33
+
34
+ define_method "set_#{level}" do
35
+ @@log_levels[level] = $stdout
36
+ end
37
+
38
+ define_method "set_#{level}_err" do
39
+ @@log_levels[level] = $stderr
40
+ end
41
+
42
+ define_method "unset_#{level}" do
43
+ @@log_levels[level] = self.nil_log
44
+ end
45
+ end
46
+
47
+ end
48
+ end
@@ -0,0 +1,29 @@
1
+ module MSPRelease
2
+ class MakeBranch
3
+
4
+ class BranchExistsError < StandardError ; end
5
+
6
+ include Exec::Helpers
7
+
8
+ attr_reader :branch_name
9
+ attr_reader :git
10
+ attr_reader :options
11
+
12
+ def initialize(git, branch_name, options={})
13
+ @git = git
14
+ @branch_name = branch_name
15
+ @options = options
16
+ end
17
+
18
+ def perform!
19
+ if git.branch_exists?(branch_name)
20
+ raise BranchExistsError, branch_name
21
+ end
22
+
23
+ git.create_and_switch(branch_name,
24
+ :start_point => options[:start_point])
25
+ end
26
+ end
27
+
28
+
29
+ end
@@ -0,0 +1,35 @@
1
+ require 'optparse'
2
+
3
+ class MSPRelease::Options
4
+
5
+ def self.get(given_args)
6
+ args = given_args.clone
7
+ opt = new(args)
8
+ [opt, args]
9
+ end
10
+
11
+ def initialize(args)
12
+ @store = {
13
+ :verbose => false
14
+ }
15
+ opts = OptionParser.new do |opts|
16
+ opts.on("-v", "--[no-]verbose", "Run with verbose output") do |v|
17
+ @store[:verbose] = v
18
+ end
19
+ end.parse!(args)
20
+ end
21
+
22
+ def [](key)
23
+ @store.fetch(key)
24
+ end
25
+
26
+ def []=(key, value)
27
+ $stderr.puts("Warning, overiding config key #{key}") if @store[key]
28
+ @store[key] = value
29
+ end
30
+
31
+ def verbose?
32
+ self[:verbose]
33
+ end
34
+
35
+ end
@@ -0,0 +1,41 @@
1
+ module MSPRelease::Project
2
+
3
+ def self.new_from_project_file(filename)
4
+ config = YAML.load_file(filename)
5
+ dirname = File.expand_path(File.dirname(filename))
6
+
7
+ project = Base.new(filename, dirname)
8
+
9
+ # TODO: make it so that this doesn't have to know about all the possible
10
+ # mixins.
11
+
12
+ # If the directory has a debian folder, treat it as a debian project.
13
+ if File.directory?(File.join(dirname, 'debian'))
14
+ project.extend(Debian)
15
+ elsif Dir.glob("#{dirname}/*.gemspec").count > 0
16
+ project.extend(Gem)
17
+ # If its a gem project, it must also be a ruby project.
18
+ project.extend(Ruby)
19
+ end
20
+
21
+ # If there is a ruby version file, we treat it as a
22
+ if config[:ruby_version_file]
23
+ project.extend(Ruby)
24
+ end
25
+
26
+ # Git project
27
+ if File.directory?(File.join(dirname, '.git'))
28
+ project.extend(Git)
29
+ end
30
+
31
+ project
32
+ end
33
+
34
+ end
35
+
36
+ require 'msp_release/project/base'
37
+ require 'msp_release/project/ruby'
38
+ require 'msp_release/project/debian'
39
+ require 'msp_release/project/gem'
40
+ require 'msp_release/project/git'
41
+
@@ -0,0 +1,63 @@
1
+ module MSPRelease
2
+ class Project::Base
3
+
4
+ include MSPRelease::Exec::Helpers
5
+
6
+ RELEASE_COMMIT_PREFIX = "RELEASE COMMIT - "
7
+
8
+ attr_reader :config, :config_file, :dir
9
+
10
+ def initialize(filename, dir='.')
11
+ @config_file = filename
12
+ @dir = dir
13
+ @config = YAML.load_file(@config_file)
14
+ config.each do |key, value|
15
+ instance_variable_set("@#{key}", value)
16
+ end
17
+ end
18
+
19
+ def release_name_from_message(commit_message)
20
+ idx = commit_message.index(RELEASE_COMMIT_PREFIX)
21
+ return nil unless idx == 0
22
+
23
+ commit_message[RELEASE_COMMIT_PREFIX.length..-1]
24
+ end
25
+
26
+ # Returns the commit message that should be used for a given release
27
+ def release_commit_message(release_name)
28
+ "#{RELEASE_COMMIT_PREFIX}#{release_name}"
29
+ end
30
+
31
+ def branch_name(version=self.version)
32
+ "release-#{version.format(:without_bugfix => true)}"
33
+ end
34
+
35
+ def at_version?(rhs_version)
36
+ any_version == rhs_version
37
+ end
38
+
39
+ def bump_version(segment)
40
+ new_version = version.bump(segment.to_sym)
41
+ written_files = write_version(new_version)
42
+ [new_version, *written_files]
43
+ end
44
+
45
+ def build(options={})
46
+ unless self.respond_to?(:build_command)
47
+ raise "Don't know how to build this project"
48
+ end
49
+
50
+ build_opts(options) if self.respond_to?(:build_opts)
51
+
52
+ LOG.debug("Checked out to #{@dir}")
53
+
54
+ LOG.debug("Building...")
55
+ build = Build.new(@dir, self, options)
56
+
57
+ result = build.perform_from_cli!
58
+ LOG.debug("Build products:")
59
+ result.files.each {|f| LOG.info(f) }
60
+ end
61
+
62
+ end
63
+ end