bosh_cli 1.2671.0 → 1.2682.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 +4 -4
- data/lib/cli.rb +8 -1
- data/lib/cli/commands/release/create_release.rb +279 -0
- data/lib/cli/commands/release/delete_release.rb +32 -0
- data/lib/cli/commands/release/init_release.rb +45 -0
- data/lib/cli/commands/release/list_releases.rb +123 -0
- data/lib/cli/commands/release/reset_release.rb +27 -0
- data/lib/cli/commands/release/upload_release.rb +208 -0
- data/lib/cli/commands/release/verify_release.rb +28 -0
- data/lib/cli/packaging_helper.rb +5 -5
- data/lib/cli/release_builder.rb +17 -23
- data/lib/cli/release_compiler.rb +12 -10
- data/lib/cli/source_control/git_ignore.rb +42 -0
- data/lib/cli/version.rb +1 -1
- data/lib/cli/versions/local_version_storage.rb +1 -1
- data/lib/cli/versions/multi_release_support.rb +24 -0
- data/lib/cli/versions/release_versions_index.rb +1 -1
- data/lib/cli/versions/releases_dir_migrator.rb +108 -0
- data/lib/cli/versions/version_file_resolver.rb +2 -11
- data/lib/cli/versions/versions_index.rb +61 -27
- metadata +19 -10
- data/lib/cli/commands/release.rb +0 -684
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6c465feaf450a9d1578bdcb091559697af1ca32d
|
4
|
+
data.tar.gz: 32eaf038e07206d75d5b13f604908df2d37eeac5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 59097f2f31a97d3103137d366fd750e413bbbd19d98aa000afcc54f0e75b08a0fb4c4674157214afde9a21a4707b9948385769ad24ac3ff90b316a173f704d2c
|
7
|
+
data.tar.gz: f1aa9306cdae45a81eefc22b6222f9e4651be01c7238a14dc4743985bc55f98f123b8f1336d103c3f015c3f11c07b0b836abb7d6fe4e85ad53cbf3be45d54696
|
data/lib/cli.rb
CHANGED
@@ -31,6 +31,8 @@ require 'zlib'
|
|
31
31
|
require 'archive/tar/minitar'
|
32
32
|
include Archive::Tar
|
33
33
|
|
34
|
+
require 'semi_semantic/version'
|
35
|
+
|
34
36
|
require 'bosh/template/evaluation_context'
|
35
37
|
|
36
38
|
unless defined?(Bosh::Cli::VERSION)
|
@@ -61,10 +63,15 @@ require 'cli/director_task'
|
|
61
63
|
require 'cli/line_wrap'
|
62
64
|
require 'cli/backup_destination_path'
|
63
65
|
|
66
|
+
require 'cli/source_control/git_ignore'
|
67
|
+
|
64
68
|
require 'cli/versions/versions_index'
|
65
69
|
require 'cli/versions/local_version_storage'
|
66
70
|
require 'cli/versions/release_versions_index'
|
71
|
+
require 'cli/versions/releases_dir_migrator'
|
67
72
|
require 'cli/versions/version_file_resolver'
|
73
|
+
require 'cli/versions/multi_release_support'
|
74
|
+
|
68
75
|
require 'cli/packaging_helper'
|
69
76
|
require 'cli/package_builder'
|
70
77
|
require 'cli/job_builder'
|
@@ -93,6 +100,6 @@ tmpdir = Dir.mktmpdir
|
|
93
100
|
at_exit { FileUtils.rm_rf(tmpdir) }
|
94
101
|
ENV['TMPDIR'] = tmpdir
|
95
102
|
|
96
|
-
Dir[File.dirname(__FILE__) + '/cli/commands
|
103
|
+
Dir[File.dirname(__FILE__) + '/cli/commands/**/*.rb'].each do |file|
|
97
104
|
require file
|
98
105
|
end
|
@@ -0,0 +1,279 @@
|
|
1
|
+
module Bosh::Cli::Command
|
2
|
+
module Release
|
3
|
+
class CreateRelease < Base
|
4
|
+
include Bosh::Cli::DependencyHelper
|
5
|
+
|
6
|
+
DEFAULT_RELEASE_NAME = 'bosh-release'
|
7
|
+
|
8
|
+
# bosh create release
|
9
|
+
usage 'create release'
|
10
|
+
desc 'Create release (assumes current directory to be a release repository)'
|
11
|
+
option '--force', 'bypass git dirty state check'
|
12
|
+
option '--final', 'create final release'
|
13
|
+
option '--with-tarball', 'create release tarball'
|
14
|
+
option '--dry-run', 'stop before writing release manifest'
|
15
|
+
option '--name NAME', 'specify a custom release name'
|
16
|
+
option '--version VERSION', 'specify a custom version number (ex: 1.0.0 or 1.0-beta.2+dev.10)'
|
17
|
+
|
18
|
+
def create(manifest_file = nil)
|
19
|
+
check_if_release_dir
|
20
|
+
|
21
|
+
migrate_to_support_multiple_releases
|
22
|
+
|
23
|
+
if manifest_file && File.file?(manifest_file)
|
24
|
+
if options[:version]
|
25
|
+
err('Cannot specify a custom version number when creating from a manifest. The manifest already specifies a version.'.make_red)
|
26
|
+
end
|
27
|
+
|
28
|
+
say('Recreating release from the manifest')
|
29
|
+
Bosh::Cli::ReleaseCompiler.compile(manifest_file, release.blobstore)
|
30
|
+
release_filename = manifest_file
|
31
|
+
else
|
32
|
+
version = options[:version]
|
33
|
+
version = Bosh::Common::Version::ReleaseVersion.parse(version).to_s unless version.nil?
|
34
|
+
|
35
|
+
release_filename = create_from_spec(version)
|
36
|
+
end
|
37
|
+
|
38
|
+
if release_filename
|
39
|
+
release.latest_release_filename = release_filename
|
40
|
+
release.save_config
|
41
|
+
end
|
42
|
+
rescue SemiSemantic::ParseError
|
43
|
+
err("Invalid version: `#{version}'. Please specify a valid version (ex: 1.0.0 or 1.0-beta.2+dev.10).".make_red)
|
44
|
+
rescue Bosh::Cli::ReleaseVersionError => e
|
45
|
+
err(e.message.make_red)
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def migrate_to_support_multiple_releases
|
51
|
+
default_release_name = release.final_name
|
52
|
+
|
53
|
+
# can't migrate without a default release name
|
54
|
+
return if default_release_name.blank?
|
55
|
+
|
56
|
+
Bosh::Cli::Versions::MultiReleaseSupport.new(@work_dir, default_release_name, self).migrate
|
57
|
+
end
|
58
|
+
|
59
|
+
def create_from_spec(version)
|
60
|
+
final = options[:final]
|
61
|
+
force = options[:force]
|
62
|
+
name = options[:name]
|
63
|
+
manifest_only = !options[:with_tarball]
|
64
|
+
dry_run = options[:dry_run]
|
65
|
+
|
66
|
+
err("Can't create final release without blobstore secret") if final && !release.has_blobstore_secret?
|
67
|
+
|
68
|
+
dirty_blob_check(force)
|
69
|
+
|
70
|
+
raise_dirty_state_error if dirty_state? && !force
|
71
|
+
|
72
|
+
if final
|
73
|
+
confirm_final_release(dry_run)
|
74
|
+
unless name
|
75
|
+
save_final_release_name if release.final_name.blank?
|
76
|
+
name = release.final_name
|
77
|
+
end
|
78
|
+
header('Building FINAL release'.make_green)
|
79
|
+
else
|
80
|
+
unless name
|
81
|
+
save_dev_release_name if release.dev_name.blank?
|
82
|
+
name = release.dev_name
|
83
|
+
end
|
84
|
+
header('Building DEV release'.make_green)
|
85
|
+
end
|
86
|
+
|
87
|
+
header('Building packages')
|
88
|
+
packages = build_packages(dry_run, final)
|
89
|
+
|
90
|
+
header('Building jobs')
|
91
|
+
jobs = build_jobs(packages.map(&:name), dry_run, final)
|
92
|
+
|
93
|
+
header('Building release')
|
94
|
+
release_builder = build_release(dry_run, final, jobs, manifest_only, packages, name, version)
|
95
|
+
|
96
|
+
header('Release summary')
|
97
|
+
show_summary(release_builder)
|
98
|
+
nl
|
99
|
+
|
100
|
+
return nil if dry_run
|
101
|
+
|
102
|
+
say("Release name: #{name.make_green}")
|
103
|
+
say("Release version: #{release_builder.version.to_s.make_green}")
|
104
|
+
say("Release manifest: #{release_builder.manifest_path.make_green}")
|
105
|
+
|
106
|
+
unless manifest_only
|
107
|
+
say("Release tarball (#{pretty_size(release_builder.tarball_path)}): " +
|
108
|
+
release_builder.tarball_path.make_green)
|
109
|
+
end
|
110
|
+
|
111
|
+
release.save_config
|
112
|
+
|
113
|
+
release_builder.manifest_path
|
114
|
+
end
|
115
|
+
|
116
|
+
def confirm_final_release(dry_run)
|
117
|
+
confirmed = non_interactive? || agree("Are you sure you want to generate #{'final'.make_red} version? ")
|
118
|
+
if !dry_run && !confirmed
|
119
|
+
say('Canceled release generation'.make_green)
|
120
|
+
exit(1)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def dirty_blob_check(force)
|
125
|
+
blob_manager.sync
|
126
|
+
if blob_manager.dirty?
|
127
|
+
blob_manager.print_status
|
128
|
+
if force
|
129
|
+
say("Proceeding with dirty blobs as '--force' is given".make_red)
|
130
|
+
else
|
131
|
+
err("Please use '--force' or upload new blobs")
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def build_packages(dry_run, final)
|
137
|
+
packages = Bosh::Cli::PackageBuilder.discover(
|
138
|
+
work_dir,
|
139
|
+
:final => final,
|
140
|
+
:blobstore => release.blobstore,
|
141
|
+
:dry_run => dry_run
|
142
|
+
)
|
143
|
+
|
144
|
+
packages.each do |package|
|
145
|
+
say("Building #{package.name.make_green}...")
|
146
|
+
package.build
|
147
|
+
nl
|
148
|
+
end
|
149
|
+
|
150
|
+
if packages.size > 0
|
151
|
+
package_index = packages.inject({}) do |index, package|
|
152
|
+
index[package.name] = package.dependencies
|
153
|
+
index
|
154
|
+
end
|
155
|
+
sorted_packages = tsort_packages(package_index)
|
156
|
+
header('Resolving dependencies')
|
157
|
+
say('Dependencies resolved, correct build order is:')
|
158
|
+
sorted_packages.each do |package_name|
|
159
|
+
say('- %s' % [package_name])
|
160
|
+
end
|
161
|
+
nl
|
162
|
+
end
|
163
|
+
|
164
|
+
packages
|
165
|
+
end
|
166
|
+
|
167
|
+
def build_release(dry_run, final, jobs, manifest_only, packages, name, version)
|
168
|
+
release_builder = Bosh::Cli::ReleaseBuilder.new(release, packages, jobs, name,
|
169
|
+
final: final,
|
170
|
+
commit_hash: commit_hash,
|
171
|
+
version: version,
|
172
|
+
uncommitted_changes: dirty_state?
|
173
|
+
)
|
174
|
+
|
175
|
+
unless dry_run
|
176
|
+
if manifest_only
|
177
|
+
release_builder.build(:generate_tarball => false)
|
178
|
+
else
|
179
|
+
release_builder.build(:generate_tarball => true)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
release_builder
|
184
|
+
end
|
185
|
+
|
186
|
+
def build_jobs(built_package_names, dry_run, final)
|
187
|
+
jobs = Bosh::Cli::JobBuilder.discover(
|
188
|
+
work_dir,
|
189
|
+
:final => final,
|
190
|
+
:blobstore => release.blobstore,
|
191
|
+
:dry_run => dry_run,
|
192
|
+
:package_names => built_package_names
|
193
|
+
)
|
194
|
+
|
195
|
+
jobs.each do |job|
|
196
|
+
say("Building #{job.name.make_green}...")
|
197
|
+
job.build
|
198
|
+
nl
|
199
|
+
end
|
200
|
+
|
201
|
+
jobs
|
202
|
+
end
|
203
|
+
|
204
|
+
def save_final_release_name
|
205
|
+
release.final_name = DEFAULT_RELEASE_NAME
|
206
|
+
if interactive?
|
207
|
+
release.final_name = ask('Please enter final release name: ').to_s
|
208
|
+
err('Canceled release creation, no name given') if release.final_name.blank?
|
209
|
+
end
|
210
|
+
release.save_config
|
211
|
+
end
|
212
|
+
|
213
|
+
def save_dev_release_name
|
214
|
+
if interactive?
|
215
|
+
release.dev_name = ask('Please enter development release name: ') do |q|
|
216
|
+
q.default = release.final_name if release.final_name
|
217
|
+
end.to_s
|
218
|
+
err('Canceled release creation, no name given') if release.dev_name.blank?
|
219
|
+
else
|
220
|
+
release.dev_name = release.final_name ? release.final_name : DEFAULT_RELEASE_NAME
|
221
|
+
end
|
222
|
+
release.save_config
|
223
|
+
end
|
224
|
+
|
225
|
+
def show_summary(builder)
|
226
|
+
packages_table = table do |t|
|
227
|
+
t.headings = %w(Name Version Notes)
|
228
|
+
builder.packages.each do |package|
|
229
|
+
t << artifact_summary(package)
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
jobs_table = table do |t|
|
234
|
+
t.headings = %w(Name Version Notes)
|
235
|
+
builder.jobs.each do |job|
|
236
|
+
t << artifact_summary(job)
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
say('Packages')
|
241
|
+
say(packages_table)
|
242
|
+
nl
|
243
|
+
say('Jobs')
|
244
|
+
say(jobs_table)
|
245
|
+
|
246
|
+
affected_jobs = builder.affected_jobs
|
247
|
+
|
248
|
+
if affected_jobs.size > 0
|
249
|
+
nl
|
250
|
+
say('Jobs affected by changes in this release')
|
251
|
+
|
252
|
+
affected_jobs_table = table do |t|
|
253
|
+
t.headings = %w(Name Version)
|
254
|
+
affected_jobs.each do |job|
|
255
|
+
t << [job.name, job.version]
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
say(affected_jobs_table)
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
def artifact_summary(artefact)
|
264
|
+
result = []
|
265
|
+
result << artefact.name
|
266
|
+
result << artefact.version
|
267
|
+
result << artefact.notes.join(', ')
|
268
|
+
result
|
269
|
+
end
|
270
|
+
|
271
|
+
def commit_hash
|
272
|
+
status = Bosh::Exec.sh('git show-ref --head --hash=8 2> /dev/null')
|
273
|
+
status.output.split.first
|
274
|
+
rescue Bosh::Exec::Error
|
275
|
+
'00000000'
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Bosh::Cli::Command
|
2
|
+
module Release
|
3
|
+
class DeleteRelease < Base
|
4
|
+
|
5
|
+
usage 'delete release'
|
6
|
+
desc 'Delete release (or a particular release version)'
|
7
|
+
option '--force', 'ignore errors during deletion'
|
8
|
+
def delete(name, version = nil)
|
9
|
+
auth_required
|
10
|
+
force = !!options[:force]
|
11
|
+
|
12
|
+
desc = "#{name}"
|
13
|
+
desc << "/#{version}" if version
|
14
|
+
|
15
|
+
if force
|
16
|
+
say("Deleting `#{desc}' (FORCED DELETE, WILL IGNORE ERRORS)".make_red)
|
17
|
+
else
|
18
|
+
say("Deleting `#{desc}'".make_red)
|
19
|
+
end
|
20
|
+
|
21
|
+
if confirmed?
|
22
|
+
status, task_id = director.delete_release(name, force: force, version: version)
|
23
|
+
task_report(status, task_id, "Deleted `#{desc}'")
|
24
|
+
else
|
25
|
+
say('Canceled deleting release'.make_green)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Bosh::Cli::Command
|
2
|
+
module Release
|
3
|
+
class InitRelease < Base
|
4
|
+
|
5
|
+
# bosh init release
|
6
|
+
usage 'init release'
|
7
|
+
desc 'Initialize release directory'
|
8
|
+
option '--git', 'initialize git repository'
|
9
|
+
def init(base = nil)
|
10
|
+
if base
|
11
|
+
FileUtils.mkdir_p(base)
|
12
|
+
Dir.chdir(base)
|
13
|
+
end
|
14
|
+
|
15
|
+
err('Release already initialized') if in_release_dir?
|
16
|
+
git_init if options[:git]
|
17
|
+
|
18
|
+
%w[config jobs packages src blobs].each do |dir|
|
19
|
+
FileUtils.mkdir(dir)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Initialize an empty blobs index
|
23
|
+
File.open(File.join('config', 'blobs.yml'), 'w') do |f|
|
24
|
+
Psych.dump({}, f)
|
25
|
+
end
|
26
|
+
|
27
|
+
say('Release directory initialized'.make_green)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def git_init
|
33
|
+
out = %x{git init 2>&1}
|
34
|
+
if $? != 0
|
35
|
+
say("error running 'git init':\n#{out}")
|
36
|
+
else
|
37
|
+
Bosh::Cli::SourceControl::GitIgnore.new(@work_dir).update
|
38
|
+
end
|
39
|
+
rescue Errno::ENOENT
|
40
|
+
say("Unable to run 'git init'".make_red)
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
module Bosh::Cli::Command
|
2
|
+
module Release
|
3
|
+
class ListReleases < Base
|
4
|
+
|
5
|
+
usage 'releases'
|
6
|
+
desc 'Show the list of available releases'
|
7
|
+
option '--jobs', 'include job templates'
|
8
|
+
def list
|
9
|
+
auth_required
|
10
|
+
releases = director.list_releases.sort do |r1, r2|
|
11
|
+
r1['name'] <=> r2['name']
|
12
|
+
end
|
13
|
+
|
14
|
+
err('No releases') if releases.empty?
|
15
|
+
|
16
|
+
currently_deployed = false
|
17
|
+
uncommited_changes = false
|
18
|
+
if releases.first.has_key? 'release_versions'
|
19
|
+
releases_table = build_releases_table(releases, options)
|
20
|
+
currently_deployed, uncommited_changes = release_version_details(releases)
|
21
|
+
elsif releases.first.has_key? 'versions'
|
22
|
+
releases_table = build_releases_table_for_old_director(releases)
|
23
|
+
currently_deployed, uncommited_changes = release_version_details_for_old_director(releases)
|
24
|
+
end
|
25
|
+
|
26
|
+
nl
|
27
|
+
say(releases_table.render)
|
28
|
+
|
29
|
+
say('(*) Currently deployed') if currently_deployed
|
30
|
+
say('(+) Uncommitted changes') if uncommited_changes
|
31
|
+
nl
|
32
|
+
say('Releases total: %d' % releases.size)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
def build_releases_table_for_old_director(releases)
|
37
|
+
table do |t|
|
38
|
+
t.headings = 'Name', 'Versions'
|
39
|
+
releases.each do |release|
|
40
|
+
versions = release['versions'].sort { |v1, v2|
|
41
|
+
Bosh::Common::Version::ReleaseVersion.parse_and_compare(v1, v2)
|
42
|
+
}.map { |v| ((release['in_use'] || []).include?(v)) ? "#{v}*" : v }
|
43
|
+
|
44
|
+
t << [release['name'], versions.join(', ')]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Builds table of release information
|
50
|
+
# Default headings: "Name", "Versions", "Commit Hash"
|
51
|
+
# Extra headings: options[:job] => "Jobs"
|
52
|
+
def build_releases_table(releases, options = {})
|
53
|
+
show_jobs = options[:jobs]
|
54
|
+
table do |t|
|
55
|
+
t.headings = 'Name', 'Versions', 'Commit Hash'
|
56
|
+
t.headings << 'Jobs' if show_jobs
|
57
|
+
releases.each do |release|
|
58
|
+
versions, commit_hashes = formatted_versions(release).transpose
|
59
|
+
row = [release['name'], versions.join("\n"), commit_hashes.join("\n")]
|
60
|
+
if show_jobs
|
61
|
+
jobs = formatted_jobs(release).transpose
|
62
|
+
row << jobs.join("\n")
|
63
|
+
end
|
64
|
+
t << row
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def formatted_versions(release)
|
70
|
+
sort_versions(release['release_versions']).map { |v| formatted_version_and_commit_hash(v) }
|
71
|
+
end
|
72
|
+
|
73
|
+
def sort_versions(versions)
|
74
|
+
versions.sort { |v1, v2| Bosh::Common::Version::ReleaseVersion.parse_and_compare(v1['version'], v2['version']) }
|
75
|
+
end
|
76
|
+
|
77
|
+
def formatted_version_and_commit_hash(version)
|
78
|
+
version_number = version['version'] + (version['currently_deployed'] ? '*' : '')
|
79
|
+
commit_hash = version['commit_hash'] + (version['uncommitted_changes'] ? '+' : '')
|
80
|
+
[version_number, commit_hash]
|
81
|
+
end
|
82
|
+
|
83
|
+
def formatted_jobs(release)
|
84
|
+
sort_versions(release['release_versions']).map do |v|
|
85
|
+
if job_names = v['job_names']
|
86
|
+
[job_names.join(', ')]
|
87
|
+
else
|
88
|
+
['n/a '] # with enough whitespace to match "Jobs" header
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
def release_version_details(releases)
|
95
|
+
currently_deployed = false
|
96
|
+
uncommitted_changes = false
|
97
|
+
releases.each do |release|
|
98
|
+
release['release_versions'].each do |version|
|
99
|
+
currently_deployed ||= version['currently_deployed']
|
100
|
+
uncommitted_changes ||= version['uncommitted_changes']
|
101
|
+
if currently_deployed && uncommitted_changes
|
102
|
+
return true, true
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
return currently_deployed, uncommitted_changes
|
107
|
+
end
|
108
|
+
|
109
|
+
def release_version_details_for_old_director(releases)
|
110
|
+
currently_deployed = false
|
111
|
+
# old director did not support uncommitted changes
|
112
|
+
uncommitted_changes = false
|
113
|
+
releases.each do |release|
|
114
|
+
currently_deployed ||= release['in_use'].any?
|
115
|
+
if currently_deployed
|
116
|
+
return true, uncommitted_changes
|
117
|
+
end
|
118
|
+
end
|
119
|
+
return currently_deployed, uncommitted_changes
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|