Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 9fadae3

Browse files
committedFeb 11, 2025·
Raise error when uncommitted changes are detected
Changes the default behavior from printing a warning to printing an error message and exiting. New CLI introduced to suppress the error and proceed with previously-default behavior.
1 parent 6f29d4e commit 9fadae3

File tree

5 files changed

+71
-6
lines changed

5 files changed

+71
-6
lines changed
 

‎lib/kamal/cli/build.rb

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@ class Kamal::Cli::Build < Kamal::Cli::Base
44
class BuildError < StandardError; end
55

66
desc "deliver", "Build app and push app image to registry then pull image on servers"
7+
option :skip_uncommitted_changes_check, type: :boolean, default: false, desc: "Skip uncommitted git changes check"
78
def deliver
89
invoke :push
910
invoke :pull
1011
end
1112

1213
desc "push", "Build and push app image to registry"
1314
option :output, type: :string, default: "registry", banner: "export_type", desc: "Exported type for the build result, and may be any exported type supported by 'buildx --output'."
15+
option :skip_uncommitted_changes_check, type: :boolean, default: false, desc: "Skip uncommitted git changes check"
1416
def push
1517
cli = self
1618

@@ -21,7 +23,12 @@ def push
2123

2224
if KAMAL.config.builder.git_clone?
2325
if uncommitted_changes.present?
24-
say "Building from a local git clone, so ignoring these uncommitted changes:\n #{uncommitted_changes}", :yellow
26+
if options[:skip_uncommitted_changes_check]
27+
say "Building from a local git clone, so ignoring these uncommitted changes:\n #{uncommitted_changes}", :yellow
28+
else
29+
say "Uncommitted changes detected - commit your changes first. To ignore uncommitted changes and deploy from latest git commit, use --skip-uncommitted-changes-check. Uncommitted changes:\n#{uncommitted_changes}", :red
30+
raise BuildError, "Uncommitted changes detected"
31+
end
2532
end
2633

2734
run_locally do

‎lib/kamal/cli/main.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ def setup
1616

1717
desc "deploy", "Deploy app to servers"
1818
option :skip_push, aliases: "-P", type: :boolean, default: false, desc: "Skip image build and push"
19+
option :skip_uncommitted_changes_check, type: :boolean, default: false, desc: "Skip uncommitted git changes check"
1920
def deploy(boot_accessories: false)
2021
runtime = print_runtime do
2122
invoke_options = deploy_options

‎test/cli/build_test.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ class CliBuildTest < CliTestCase
2020
.with(:git, "-C", anything, :status, "--porcelain")
2121
.returns("")
2222

23+
stub_no_uncommitted_git_changes
24+
2325
run_command("push", "--verbose").tap do |output|
2426
assert_hook_ran "pre-build", output
2527
assert_match /Cloning repo into build directory/, output
@@ -42,6 +44,8 @@ class CliBuildTest < CliTestCase
4244
.with(:git, "-C", anything, :status, "--porcelain")
4345
.returns("")
4446

47+
stub_no_uncommitted_git_changes
48+
4549
run_command("push", "--output=docker", "--verbose").tap do |output|
4650
assert_hook_ran "pre-build", output
4751
assert_match /Cloning repo into build directory/, output
@@ -80,6 +84,8 @@ class CliBuildTest < CliTestCase
8084
.with(:git, "-C", anything, :status, "--porcelain")
8185
.returns("")
8286

87+
stub_no_uncommitted_git_changes
88+
8389
run_command("push", "--verbose").tap do |output|
8490
assert_match /Cloning repo into build directory/, output
8591
assert_match /Resetting local clone/, output
@@ -124,6 +130,8 @@ class CliBuildTest < CliTestCase
124130

125131
Dir.stubs(:chdir)
126132

133+
stub_no_uncommitted_git_changes
134+
127135
run_command("push", "--verbose") do |output|
128136
assert_match /Cloning repo into build directory `#{build_directory}`\.\.\..*Cloning repo into build directory `#{build_directory}`\.\.\./, output
129137
assert_match "Resetting local clone as `#{build_directory}` already exists...", output
@@ -162,6 +170,8 @@ class CliBuildTest < CliTestCase
162170
SSHKit::Backend::Abstract.any_instance.expects(:execute)
163171
.with(:docker, :buildx, :build, "--output=type=registry", "--platform", "linux/amd64", "--builder", "kamal-local-docker-container", "-t", "dhh/app:999", "-t", "dhh/app:latest", "--label", "service=\"app\"", "--file", "Dockerfile", ".")
164172

173+
stub_no_uncommitted_git_changes
174+
165175
run_command("push").tap do |output|
166176
assert_match /WARN Missing compatible builder, so creating a new one first/, output
167177
end

‎test/cli/main_test.rb

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ class CliMainTest < CliTestCase
4343

4444
test "deploy" do
4545
with_test_secrets("secrets" => "DB_PASSWORD=secret") do
46-
invoke_options = { "config_file" => "test/fixtures/deploy_simple.yml", "version" => "999", "skip_hooks" => false, "verbose" => true }
46+
invoke_options = { "config_file" => "test/fixtures/deploy_simple.yml", "version" => "999", "skip_hooks" => false, "skip_uncommitted_changes_check" => false, "verbose" => true }
4747

4848
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:registry:login", [], invoke_options.merge(skip_local: false))
4949
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:build:deliver", [], invoke_options)
@@ -68,7 +68,7 @@ class CliMainTest < CliTestCase
6868
end
6969

7070
test "deploy with skip_push" do
71-
invoke_options = { "config_file" => "test/fixtures/deploy_simple.yml", "version" => "999", "skip_hooks" => false }
71+
invoke_options = { "config_file" => "test/fixtures/deploy_simple.yml", "version" => "999", "skip_hooks" => false, "skip_uncommitted_changes_check" => false }
7272

7373
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:registry:login", [], invoke_options.merge(skip_local: true))
7474
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:build:pull", [], invoke_options)
@@ -88,6 +88,45 @@ class CliMainTest < CliTestCase
8888
end
8989
end
9090

91+
test "deploy with uncommitted git changes" do
92+
Kamal::Git.stubs(:uncommitted_changes).returns("M file\n")
93+
94+
with_argv([ "deploy", "-c", "test/fixtures/deploy_simple.yml" ]) do
95+
output = capture(:stdout) do
96+
begin
97+
Kamal::Cli::Main.start
98+
rescue Kamal::Cli::Build::BuildError => e
99+
@raised_error = e
100+
end
101+
end
102+
assert_match /Uncommitted changes detected - commit your changes first. To ignore uncommitted changes and deploy from latest git commit, use --skip-uncommitted-changes-check. Uncommitted changes:\nM file/, output
103+
end
104+
assert_equal Kamal::Cli::Build::BuildError, @raised_error.class
105+
assert_equal "Uncommitted changes detected", @raised_error.message
106+
end
107+
108+
test "deploy with uncommitted git changes and skip_uncommitted_changes_check" do
109+
invoke_options = { "config_file" => "test/fixtures/deploy_simple.yml", "version" => "999", "skip_hooks" => false, "skip_uncommitted_changes_check" => true }
110+
111+
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:registry:login", [], invoke_options.merge(skip_local: false))
112+
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:build:deliver", [], invoke_options)
113+
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:proxy:boot", [], invoke_options)
114+
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:app:stale_containers", [], invoke_options.merge(stop: true))
115+
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:app:boot", [], invoke_options)
116+
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:prune:all", [], invoke_options)
117+
118+
Kamal::Git.stubs(:uncommitted_changes).returns("M file\n")
119+
120+
run_command("deploy", "--skip-uncommitted-changes-check").tap do |output|
121+
assert_match /Acquiring the deploy lock/, output
122+
assert_match /Log into image registry/, output
123+
assert_match /Ensure kamal-proxy is running/, output
124+
assert_match /Detect stale containers/, output
125+
assert_match /Prune old containers and images/, output
126+
assert_match /Releasing the deploy lock/, output
127+
end
128+
end
129+
91130
test "deploy when locked" do
92131
Thread.report_on_exception = false
93132

@@ -117,6 +156,8 @@ class CliMainTest < CliTestCase
117156
.returns("")
118157
.at_least_once
119158

159+
stub_no_uncommitted_git_changes
160+
120161
assert_raises(Kamal::Cli::LockError) do
121162
run_command("deploy")
122163
end
@@ -148,13 +189,15 @@ class CliMainTest < CliTestCase
148189
.returns("")
149190
.at_least_once
150191

192+
stub_no_uncommitted_git_changes
193+
151194
assert_raises(SSHKit::Runner::ExecuteError) do
152195
run_command("deploy")
153196
end
154197
end
155198

156199
test "deploy errors during outside section leave remove lock" do
157-
invoke_options = { "config_file" => "test/fixtures/deploy_simple.yml", "version" => "999", "skip_hooks" => false, :skip_local => false }
200+
invoke_options = { "config_file" => "test/fixtures/deploy_simple.yml", "version" => "999", "skip_hooks" => false, "skip_uncommitted_changes_check" => false, :skip_local => false }
158201

159202
Kamal::Cli::Main.any_instance.expects(:invoke)
160203
.with("kamal:cli:registry:login", [], invoke_options.merge(skip_local: false))
@@ -168,7 +211,7 @@ class CliMainTest < CliTestCase
168211
end
169212

170213
test "deploy with skipped hooks" do
171-
invoke_options = { "config_file" => "test/fixtures/deploy_simple.yml", "version" => "999", "skip_hooks" => true }
214+
invoke_options = { "config_file" => "test/fixtures/deploy_simple.yml", "version" => "999", "skip_hooks" => true, "skip_uncommitted_changes_check" => false }
172215

173216
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:registry:login", [], invoke_options.merge(skip_local: false))
174217
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:build:deliver", [], invoke_options)
@@ -183,7 +226,7 @@ class CliMainTest < CliTestCase
183226
end
184227

185228
test "deploy with missing secrets" do
186-
invoke_options = { "config_file" => "test/fixtures/deploy_with_secrets.yml", "version" => "999", "skip_hooks" => false }
229+
invoke_options = { "config_file" => "test/fixtures/deploy_with_secrets.yml", "version" => "999", "skip_hooks" => false, "skip_uncommitted_changes_check" => false }
187230

188231
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:registry:login", [], invoke_options.merge(skip_local: false))
189232
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:build:deliver", [], invoke_options)

‎test/test_helper.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,10 @@ def teardown_test_secrets
7575
Dir.chdir(@original_pwd)
7676
FileUtils.rm_rf(@secrets_tmpdir)
7777
end
78+
79+
def stub_no_uncommitted_git_changes
80+
Kamal::Git.stubs(:uncommitted_changes).returns("")
81+
end
7882
end
7983

8084
class SecretAdapterTestCase < ActiveSupport::TestCase

0 commit comments

Comments
 (0)
Please sign in to comment.