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