vershunt 2.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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