release_manager 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.bash_profile +27 -0
- data/.dockerignore +1 -0
- data/.env +3 -0
- data/.gitignore +13 -0
- data/.rspec +2 -0
- data/CHANGELOG.md +55 -0
- data/Dockerfile +14 -0
- data/Gemfile +14 -0
- data/Gemfile.lock +72 -0
- data/LICENSE.txt +21 -0
- data/README.md +311 -0
- data/Rakefile +6 -0
- data/app_startup_script.sh +4 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/docker-compose.yml +37 -0
- data/exe/bump-changelog +5 -0
- data/exe/deploy-mod +5 -0
- data/exe/release-mod +5 -0
- data/exe/sandbox-create +13 -0
- data/lib/release_manager.rb +33 -0
- data/lib/release_manager/changelog.rb +130 -0
- data/lib/release_manager/cli/deploy_mod_cli.rb +44 -0
- data/lib/release_manager/cli/release_mod_cli.rb +43 -0
- data/lib/release_manager/cli/sandbox_create_cli.rb +138 -0
- data/lib/release_manager/control_mod.rb +83 -0
- data/lib/release_manager/control_repo.rb +35 -0
- data/lib/release_manager/errors.rb +13 -0
- data/lib/release_manager/git/credentials.rb +98 -0
- data/lib/release_manager/git/utilites.rb +263 -0
- data/lib/release_manager/logger.rb +52 -0
- data/lib/release_manager/module_deployer.rb +77 -0
- data/lib/release_manager/puppet_module.rb +211 -0
- data/lib/release_manager/puppetfile.rb +148 -0
- data/lib/release_manager/release.rb +174 -0
- data/lib/release_manager/sandbox.rb +272 -0
- data/lib/release_manager/vcs_manager.rb +22 -0
- data/lib/release_manager/vcs_manager/gitlab_adapter.rb +112 -0
- data/lib/release_manager/vcs_manager/vcs_adapter.rb +22 -0
- data/lib/release_manager/version.rb +3 -0
- data/lib/release_manager/workflow_action.rb +5 -0
- data/release_manager.gemspec +38 -0
- data/setup_repos.rb +95 -0
- metadata +175 -0
@@ -0,0 +1,83 @@
|
|
1
|
+
class ControlMod
|
2
|
+
attr_reader :name, :metadata, :repo
|
3
|
+
attr_accessor :version
|
4
|
+
|
5
|
+
def initialize(name, args)
|
6
|
+
@name = name
|
7
|
+
@metadata = args.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}
|
8
|
+
end
|
9
|
+
|
10
|
+
def repo
|
11
|
+
git_url
|
12
|
+
end
|
13
|
+
|
14
|
+
def git_url
|
15
|
+
metadata[:git]
|
16
|
+
end
|
17
|
+
|
18
|
+
def branch
|
19
|
+
metadata[:branch]
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_json(state = nil)
|
23
|
+
metadata.to_json(state)
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_s
|
27
|
+
name_line = "mod '#{name}',"
|
28
|
+
data = metadata.map { |k, v| ":#{k} => '#{v}'" }.join(",\n\ ")
|
29
|
+
"#{name_line}\n #{data}"
|
30
|
+
end
|
31
|
+
|
32
|
+
def bump_patch_version
|
33
|
+
return unless metadata[:tag]
|
34
|
+
pieces = metadata[:tag].split('.')
|
35
|
+
raise "invalid semver structure #{metadata[:tag]}" if pieces.count != 3
|
36
|
+
pieces[2] = pieces[2].next
|
37
|
+
pin_version(pieces.join('.'))
|
38
|
+
end
|
39
|
+
|
40
|
+
def bump_minor_version
|
41
|
+
return unless metadata[:tag]
|
42
|
+
pieces = metadata[:tag].split('.')
|
43
|
+
raise "invalid semver structure #{metadata[:tag]}" if pieces.count != 3
|
44
|
+
pieces[2] = '0'
|
45
|
+
pieces[1] = pieces[1].next
|
46
|
+
pin_version(pieces.join('.'))
|
47
|
+
end
|
48
|
+
|
49
|
+
def bump_major_version
|
50
|
+
return unless metadata[:tag]
|
51
|
+
pieces = metadata[:tag].split('.')
|
52
|
+
raise "invalid semver structure #{metadata[:tag]}" if pieces.count != 3
|
53
|
+
pieces[2] = '0'
|
54
|
+
pieces[1] = '0'
|
55
|
+
pieces[0] = pieces[0].next
|
56
|
+
pin_version(pieces.join('.'))
|
57
|
+
end
|
58
|
+
|
59
|
+
def version
|
60
|
+
metadata[:tag]
|
61
|
+
end
|
62
|
+
|
63
|
+
def version=(v)
|
64
|
+
metadata[:tag] = v
|
65
|
+
end
|
66
|
+
|
67
|
+
def pin_version(v)
|
68
|
+
metadata.delete(:ref)
|
69
|
+
metadata.delete(:branch)
|
70
|
+
metadata[:tag] = v
|
71
|
+
end
|
72
|
+
|
73
|
+
def pin_branch(name)
|
74
|
+
metadata[:branch] = name
|
75
|
+
metadata.delete(:ref)
|
76
|
+
metadata.delete(:tag)
|
77
|
+
end
|
78
|
+
|
79
|
+
def pin_url(src)
|
80
|
+
metadata[:git] = src
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'release_manager/puppetfile'
|
2
|
+
require 'rugged'
|
3
|
+
require 'release_manager/git/utilites'
|
4
|
+
|
5
|
+
class ControlRepo
|
6
|
+
attr_accessor :path, :repo, :url
|
7
|
+
|
8
|
+
include ReleaseManager::Git::Utilities
|
9
|
+
include ReleaseManager::Logger
|
10
|
+
|
11
|
+
def initialize(path, url = nil)
|
12
|
+
@path = path
|
13
|
+
@url = url
|
14
|
+
end
|
15
|
+
|
16
|
+
# @return [ControlRepo] - creates a new control repo object and clones the url unless already cloned
|
17
|
+
def self.create(path, url)
|
18
|
+
c = ControlRepo.new(path, url)
|
19
|
+
c.clone(url, path)
|
20
|
+
c
|
21
|
+
end
|
22
|
+
|
23
|
+
def repo
|
24
|
+
@repo ||= ::Rugged::Repository.new(path)
|
25
|
+
end
|
26
|
+
|
27
|
+
def puppetfile
|
28
|
+
unless @puppetfile
|
29
|
+
@puppetfile = Puppetfile.new(File.join(path, 'Puppetfile'))
|
30
|
+
@puppetfile.base_path = path
|
31
|
+
end
|
32
|
+
@puppetfile
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class ModNotFoundException < Exception; end
|
2
|
+
class InvalidModuleNameException < Exception; end
|
3
|
+
class PuppetfileNotFoundException < Exception; end
|
4
|
+
class InvalidPuppetfileException < Exception; end
|
5
|
+
class InvalidMetadataSource < Exception; end
|
6
|
+
class NoUnreleasedLine < Exception; end
|
7
|
+
class NoChangeLogFile < Exception; end
|
8
|
+
class UpstreamSourceMatch < Exception; end
|
9
|
+
class GitError < Exception; end
|
10
|
+
class RepoNotFound < Exception; end
|
11
|
+
class InvalidModule < Exception; end
|
12
|
+
class InvalidToken < Exception; end
|
13
|
+
class InvalidSshkey < Exception; end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'rugged'
|
2
|
+
require 'release_manager/logger'
|
3
|
+
require 'io/console'
|
4
|
+
# Generate credentials for secured remote connections.
|
5
|
+
module ReleaseManager
|
6
|
+
module Git
|
7
|
+
class Credentials
|
8
|
+
include ReleaseManager::Logger
|
9
|
+
|
10
|
+
# @param repository [Rugged::BaseRepository]
|
11
|
+
def initialize(repository = nil)
|
12
|
+
@repository = repository
|
13
|
+
@called = 0
|
14
|
+
end
|
15
|
+
|
16
|
+
def needs_auth?(url)
|
17
|
+
url =~ /\Agit@/
|
18
|
+
end
|
19
|
+
|
20
|
+
def call(url, username_from_url = 'git', allowed_types = [:ssh_key])
|
21
|
+
@called += 1
|
22
|
+
# Break out of infinite HTTP auth retry loop introduced in libgit2/rugged 0.24.0, libssh
|
23
|
+
# auth seems to already abort after ~50 attempts.
|
24
|
+
if @called > 50
|
25
|
+
raise Exception.new("Authentication failed for Git remote %{url}.") % {url: url.inspect}
|
26
|
+
end
|
27
|
+
if allowed_types.include?(:ssh_key)
|
28
|
+
# should also check to see if process is still alive
|
29
|
+
begin
|
30
|
+
if ENV['SSH_AUTH_SOCK'] or (ENV['SSH_AGENT_PID'] and Process.getpgid( ENV['SSH_AGENT_PID'].to_i ))
|
31
|
+
ssh_agent_credentials
|
32
|
+
else
|
33
|
+
logger.warn("Could not find ssh-agent running, falling back to ssh key")
|
34
|
+
ssh_key_credentials
|
35
|
+
end
|
36
|
+
rescue Errno::ESRCH
|
37
|
+
ssh_key_credentials
|
38
|
+
end
|
39
|
+
else
|
40
|
+
default_credentials
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def prompt_for_password
|
45
|
+
print "Enter password for #{global_private_key}: "
|
46
|
+
STDIN.noecho(&:gets).chomp
|
47
|
+
end
|
48
|
+
|
49
|
+
# this assumes the user has the private key in their home folder
|
50
|
+
# we should be smarter about getting this, maybe consulting ssh
|
51
|
+
# directory to find out which key is for the host if using an ssh config file
|
52
|
+
# additionally if the key is password protected how do we prompt for the password?
|
53
|
+
def global_private_key
|
54
|
+
unless @global_private_key
|
55
|
+
@global_private_key = ENV['SSH_PRIVATE_KEY'] || File.expand_path(File.join(ENV['HOME'], '.ssh', 'id_rsa'))
|
56
|
+
logger.info("Using ssh private key #{@global_private_key}")
|
57
|
+
end
|
58
|
+
@global_private_key
|
59
|
+
end
|
60
|
+
|
61
|
+
def global_public_key
|
62
|
+
unless @global_public_key
|
63
|
+
@global_public_key = ENV['SSH_PUBLIC_KEY'] || File.expand_path(File.join(ENV['HOME'], '.ssh', 'id_rsa.pub'))
|
64
|
+
logger.info("Using ssh public key #{@global_public_key}")
|
65
|
+
end
|
66
|
+
@global_public_key
|
67
|
+
end
|
68
|
+
|
69
|
+
# SSH_AGENT_SOCK must be set
|
70
|
+
def ssh_agent_credentials
|
71
|
+
Rugged::Credentials::SshKeyFromAgent.new(username: git_username)
|
72
|
+
end
|
73
|
+
|
74
|
+
# this method does currently now work
|
75
|
+
def ssh_key_credentials(url = nil)
|
76
|
+
logger.error("Must use ssh-agent, please run ssh-agent zsh, then ssh-add to load your ssh key")
|
77
|
+
exit 1
|
78
|
+
unless File.readable?(global_private_key)
|
79
|
+
raise Exception.new("Unable to use SSH key auth for %{url}: private key %{private_key} is missing or unreadable" % {url: url.inspect, private_key: global_private_key.inspect} )
|
80
|
+
end
|
81
|
+
Rugged::Credentials::SshKey.new(:username => git_username,
|
82
|
+
:privatekey => global_private_key,
|
83
|
+
:publickey => global_public_key,
|
84
|
+
:passphrase => prompt_for_password)
|
85
|
+
end
|
86
|
+
|
87
|
+
def default_credentials
|
88
|
+
Rugged::Credentials::Default.new
|
89
|
+
end
|
90
|
+
|
91
|
+
def git_username
|
92
|
+
'git'
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
@@ -0,0 +1,263 @@
|
|
1
|
+
require 'release_manager/git/credentials'
|
2
|
+
require 'uri'
|
3
|
+
|
4
|
+
module ReleaseManager
|
5
|
+
module Git
|
6
|
+
module Utilities
|
7
|
+
|
8
|
+
def repo
|
9
|
+
@repo ||= Rugged::Repository.new(path)
|
10
|
+
end
|
11
|
+
|
12
|
+
# @param [String] remote_name - the name of the remote
|
13
|
+
def fetch(remote_name = 'upstream')
|
14
|
+
return unless remote_exists?(remote_name)
|
15
|
+
remote = repo.remotes[remote_name]
|
16
|
+
logger.info("Fetching remote #{remote_name} from #{remote.url}")
|
17
|
+
remote.fetch({
|
18
|
+
#progress: lambda { |output| puts output },
|
19
|
+
credentials: credentials.call(remote.url)
|
20
|
+
})
|
21
|
+
end
|
22
|
+
|
23
|
+
def transports
|
24
|
+
[:ssh, :https].each do |transport|
|
25
|
+
unless ::Rugged.features.include?(transport)
|
26
|
+
logger.warn("Rugged has been compiled without support for %{transport}; Git repositories will not be reachable via %{transport}. Try installing libssh-devel") % {transport: transport}
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def credentials
|
32
|
+
@credentials ||= ReleaseManager::Git::Credentials.new(nil)
|
33
|
+
end
|
34
|
+
|
35
|
+
# @param [String] branch - the name of the branch you want checked out when cloning
|
36
|
+
# @param [String] url - the url to clone
|
37
|
+
# @return [Rugged::Repository] - the clond repository
|
38
|
+
# Clones the url
|
39
|
+
# if the clone path already exists, nothing is done
|
40
|
+
def clone(url, path)
|
41
|
+
if File.exists?(File.join(path, '.git'))
|
42
|
+
add_remote(url, 'upstream')
|
43
|
+
fetch('upstream')
|
44
|
+
repo
|
45
|
+
else
|
46
|
+
logger.info("Cloning repo with url: #{url} to #{path}")
|
47
|
+
r = Rugged::Repository.clone_at(url, path, {
|
48
|
+
#progress: lambda { |output| puts output },
|
49
|
+
credentials: credentials.call(url)
|
50
|
+
})
|
51
|
+
r
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# @param [String] url - the url of the remote
|
56
|
+
# @param [String] remote_name - the name of the remote
|
57
|
+
# @param [Boolean] reset_url - set to true if you wish to reset the remote url
|
58
|
+
# @return [Rugged::Remote] a rugged remote object
|
59
|
+
def add_remote(url, remote_name = 'upstream', reset_url = false )
|
60
|
+
return unless git_url?(url)
|
61
|
+
if remote_exists?(remote_name)
|
62
|
+
# ensure the correct url is set
|
63
|
+
# this sets a non persistant fetch url
|
64
|
+
unless remote_url_matches?(remote_name, url)
|
65
|
+
if reset_url
|
66
|
+
logger.info("Resetting #{remote_name} remote to #{url} for #{path}")
|
67
|
+
repo.remotes.set_url(remote_name,url)
|
68
|
+
repo.remotes[remote_name]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
else
|
72
|
+
logger.info("Adding #{remote_name} remote to #{url} for #{path}")
|
73
|
+
repo.remotes.create(remote_name, url)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# @param [String] name - the name of the remote
|
78
|
+
# @return [Boolean] - return true if the remote name and url are defined in the git repo
|
79
|
+
def remote_exists?(name)
|
80
|
+
repo.remotes[name]
|
81
|
+
end
|
82
|
+
|
83
|
+
# @param [String] name - the name of the remote
|
84
|
+
# @param [String] url - the url of the remote
|
85
|
+
# @return [Boolean] - true if the url matches a remote url already defined
|
86
|
+
def remote_url_matches?(name, url)
|
87
|
+
repo.remotes[name].url.eql?(url)
|
88
|
+
end
|
89
|
+
|
90
|
+
# @param [String] name - the name of the branch
|
91
|
+
# @return [Boolean] - true if the branch exist
|
92
|
+
def branch_exist?(name)
|
93
|
+
repo.branches.exist?(name)
|
94
|
+
end
|
95
|
+
|
96
|
+
# we should be creating the branch from upstream
|
97
|
+
# @return [Rugged::Branch]
|
98
|
+
def create_branch(name, target = 'upstream/master')
|
99
|
+
# fetch the remote if defined in the target
|
100
|
+
unless branch_exist?(name)
|
101
|
+
fetch(target.split('/').first) if target.include?('/')
|
102
|
+
logger.info("Creating branch: #{name} for #{path}")
|
103
|
+
repo.create_branch(name, target)
|
104
|
+
else
|
105
|
+
repo.branches[name]
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# deletes the branch with the given name
|
110
|
+
# @param [String] name - the name of the branch to delete
|
111
|
+
def delete_branch(name)
|
112
|
+
repo.branches.delete(name)
|
113
|
+
end
|
114
|
+
|
115
|
+
# @param [String] remote_name - the remote name to push the branch to
|
116
|
+
def push_branch(remote_name, branch)
|
117
|
+
remote = find_or_create_remote(remote_name)
|
118
|
+
refs = [repo.branches[branch].canonical_name]
|
119
|
+
logger.info("Pushing branch #{branch} to remote #{remote.url}")
|
120
|
+
remote.push(refs, credentials: credentials)
|
121
|
+
end
|
122
|
+
|
123
|
+
# push all the tags to the remote
|
124
|
+
# @param [String] remote_name - the remote name to push tags to
|
125
|
+
def push_tags(remote_name)
|
126
|
+
remote = find_or_create_remote(remote_name)
|
127
|
+
refs = repo.tags.map(&:canonical_name)
|
128
|
+
logger.info("Pushing tags to remote #{remote.url}")
|
129
|
+
remote.push(refs, credentials: credentials)
|
130
|
+
end
|
131
|
+
|
132
|
+
# @return [String] the name of the current branch
|
133
|
+
def current_branch
|
134
|
+
repo.head.name.sub(/^refs\/heads\//, '')
|
135
|
+
end
|
136
|
+
|
137
|
+
def checkout_branch(name)
|
138
|
+
if current_branch != name
|
139
|
+
logger.info("Checking out branch: #{name} for #{path}")
|
140
|
+
repo.checkout(name)
|
141
|
+
else
|
142
|
+
# already checked out
|
143
|
+
logger.debug("Currently on branch #{name} for #{path}")
|
144
|
+
repo.branches[name]
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
# @param [String] remote_name - the remote name
|
149
|
+
# @return [Rugged::Remote] the remote object
|
150
|
+
# find the remote or create a new remote with the name as source
|
151
|
+
def find_or_create_remote(remote_name)
|
152
|
+
remote_from_name(remote_name) ||
|
153
|
+
remote_from_url(remote_name) ||
|
154
|
+
add_remote(remote_name, 'source', true)
|
155
|
+
end
|
156
|
+
|
157
|
+
# @param [String] name - the remote name to push the branch to
|
158
|
+
# @return [Rugged::Remote] the remote object if found
|
159
|
+
# Given the url find the remote with that url
|
160
|
+
def remote_from_name(name)
|
161
|
+
repo.remotes.find { |r| r.name.eql?(name) } unless git_url?(name)
|
162
|
+
end
|
163
|
+
|
164
|
+
# @param [String] url - the remote url to push the branch to
|
165
|
+
# @return [Rugged::Remote] the remote object if found
|
166
|
+
# Given the url find the remote with that url
|
167
|
+
def remote_from_url(url)
|
168
|
+
repo.remotes.find { |r| r.url.eql?(url) } if git_url?(url)
|
169
|
+
end
|
170
|
+
|
171
|
+
# @param [String] name - the remote name or url to check
|
172
|
+
# @return [MatchData] MatchData if the remote name is a url
|
173
|
+
# Is the name actually a url?
|
174
|
+
def git_url?(name)
|
175
|
+
/((git|ssh|http(s)?)|(git@[\w\.]+))(:(\/\/)?)([\w\.@\:\/\-~]+)(\.git)(\/)?/.match(name)
|
176
|
+
end
|
177
|
+
|
178
|
+
# @return [String] - the author name found in the config
|
179
|
+
def author_name
|
180
|
+
repo.config.get('user.name') || Rugged::Config.global.get('user.name')
|
181
|
+
end
|
182
|
+
|
183
|
+
# @return [String] - the author email found in the config
|
184
|
+
def author_email
|
185
|
+
repo.config.get('user.email') || Rugged::Config.global.get('user.email')
|
186
|
+
end
|
187
|
+
|
188
|
+
# @return [Hash] the author information used in a commit message
|
189
|
+
def author
|
190
|
+
{:email=>author_email, :time=>Time.now, :name=>author_name}
|
191
|
+
end
|
192
|
+
|
193
|
+
# # @param [String] file - the path to the file you want to add
|
194
|
+
# def add_file(file)
|
195
|
+
# return unless File.exists?(file)
|
196
|
+
# index = repo.index
|
197
|
+
# file.slice!(repo.workdir)
|
198
|
+
# index.add(:path => file, :oid => Rugged::Blob.from_workdir(repo, file), :mode => 0100644)
|
199
|
+
# index.write
|
200
|
+
# end
|
201
|
+
|
202
|
+
# @param [String] file - the path to the file you want to add
|
203
|
+
def add_file(file)
|
204
|
+
# TODO: change this to rugged implementation
|
205
|
+
`git --work-tree=#{path} --git-dir=#{repo.path} add #{file}`
|
206
|
+
end
|
207
|
+
|
208
|
+
# @param [String] file - the path to the file you want to remove
|
209
|
+
def remove_file(file)
|
210
|
+
index = repo.index
|
211
|
+
File.unlink(file)
|
212
|
+
index.remove(file)
|
213
|
+
index.write
|
214
|
+
end
|
215
|
+
|
216
|
+
# # @param [String] message - the message you want in the commit
|
217
|
+
# def create_commit(message)
|
218
|
+
# # get the index for this repository
|
219
|
+
# logger.info repo.status { |file, status_data| puts "#{file} has status: #{status_data.inspect}" }
|
220
|
+
#
|
221
|
+
# index = repo.index
|
222
|
+
# index.read_tree repo.head.target.tree unless repo.empty?
|
223
|
+
# require 'pry'; binding.pry
|
224
|
+
# #repo.lookup
|
225
|
+
# tree_new = index.write_tree repo
|
226
|
+
# oid = Rugged::Commit.create(repo,
|
227
|
+
# author: author,
|
228
|
+
# message: message,
|
229
|
+
# committer: author,
|
230
|
+
# parents: repo.empty? ? [] : [repo.head.target].compact,
|
231
|
+
# tree: tree_new,
|
232
|
+
# update_ref: 'HEAD')
|
233
|
+
# logger.info("Created commit #{oid} with #{message}")
|
234
|
+
# index.write
|
235
|
+
# #repo.status { |file, status_data| puts "#{file} has status: #{status_data.inspect}" }
|
236
|
+
# oid
|
237
|
+
# end
|
238
|
+
|
239
|
+
# @param [String] message - the message you want in the commit
|
240
|
+
# TODO: change this to rugged implementation
|
241
|
+
def create_commit(message)
|
242
|
+
output = `git --work-tree=#{path} --git-dir=#{repo.path} commit --message '#{message}' 2>&1`
|
243
|
+
if $?.success?
|
244
|
+
logger.info("Created commit #{message}")
|
245
|
+
else
|
246
|
+
logger.error output
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
# @return [String] the current branch name
|
251
|
+
def current_branch
|
252
|
+
repo.head.name.sub(/^refs\/heads\//, '')
|
253
|
+
end
|
254
|
+
|
255
|
+
def cherry_pick(commit)
|
256
|
+
return unless commit
|
257
|
+
repo.cherrypick(commit)
|
258
|
+
logger.info("Cherry picking commit with id: #{commit}")
|
259
|
+
end
|
260
|
+
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|