diff --git a/extensions/MozChangeField/Extension.pm b/extensions/MozChangeField/Extension.pm
index 6e2dbffdae..860b0fdad7 100644
--- a/extensions/MozChangeField/Extension.pm
+++ b/extensions/MozChangeField/Extension.pm
@@ -34,18 +34,22 @@ my @pre_instances = (
use Bugzilla::Extension::MozChangeField::Post::SeverityS1PriorityP1;
use Bugzilla::Extension::MozChangeField::Post::ClearTrackingPriorityS1;
+
#use Bugzilla::Extension::MozChangeField::Post::CommentOnSeverity;
use Bugzilla::Extension::MozChangeField::Post::SetTrackingSeverityS1;
use Bugzilla::Extension::MozChangeField::Post::TypePriSevEditbugs;
use Bugzilla::Extension::MozChangeField::Post::RegressedByTypeKeyword;
+use Bugzilla::Extension::MozChangeField::Post::CheckinNeededTbMilestone;
my @post_instances = (
Bugzilla::Extension::MozChangeField::Post::SeverityS1PriorityP1->new,
Bugzilla::Extension::MozChangeField::Post::ClearTrackingPriorityS1->new,
+
#Bugzilla::Extension::MozChangeField::Post::CommentOnSeverity->new,
Bugzilla::Extension::MozChangeField::Post::SetTrackingSeverityS1->new,
Bugzilla::Extension::MozChangeField::Post::TypePriSevEditbugs->new,
Bugzilla::Extension::MozChangeField::Post::RegressedByTypeKeyword->new,
+ Bugzilla::Extension::MozChangeField::Post::CheckinNeededTbMilestone->new,
);
our $VERSION = '0.1';
diff --git a/extensions/MozChangeField/lib/Post/CheckinNeededTbMilestone.pm b/extensions/MozChangeField/lib/Post/CheckinNeededTbMilestone.pm
new file mode 100644
index 0000000000..d52db2b615
--- /dev/null
+++ b/extensions/MozChangeField/lib/Post/CheckinNeededTbMilestone.pm
@@ -0,0 +1,53 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This Source Code Form is "Incompatible With Secondary Licenses", as
+# defined by the Mozilla Public License, v. 2.0.
+
+package Bugzilla::Extension::MozChangeField::Post::CheckinNeededTbMilestone;
+
+use 5.10.1;
+use Moo;
+
+use Bugzilla::Error;
+
+use constant PRODUCTS =>
+ {map { $_ => 1 } ('Thunderbird', 'MailNews Core', 'Calendar')};
+
+use constant KEYWORD => 'checkin-needed-tb';
+
+sub _check_milestone {
+ my ($bug) = @_;
+ my $product = $bug->product_obj;
+
+ return if !PRODUCTS->{$product->name};
+ return if !$bug->has_keyword(KEYWORD);
+
+ # The product's default milestone (normally '---') means 'not set'.
+ return if $bug->target_milestone ne $product->default_milestone;
+
+ ThrowUserError('mozchangefield_checkin_needed_tb_milestone',
+ {keyword => KEYWORD});
+}
+
+sub evaluate_create {
+ my ($self, $args) = @_;
+ _check_milestone($args->{bug});
+}
+
+sub evaluate_change {
+ my ($self, $args) = @_;
+ my $changes = $args->{changes};
+
+ # Only enforce on a save that touches one of three fields, so bugs
+ # already in this state don't block unrelated edits.
+ return
+ if !exists $changes->{keywords}
+ && !exists $changes->{target_milestone}
+ && !exists $changes->{product};
+
+ _check_milestone($args->{bug});
+}
+
+1;
diff --git a/extensions/MozChangeField/template/en/default/hook/global/user-error-errors.html.tmpl b/extensions/MozChangeField/template/en/default/hook/global/user-error-errors.html.tmpl
index 62ca30ec20..777ecc516f 100644
--- a/extensions/MozChangeField/template/en/default/hook/global/user-error-errors.html.tmpl
+++ b/extensions/MozChangeField/template/en/default/hook/global/user-error-errors.html.tmpl
@@ -14,4 +14,9 @@
[% title = "Invalid Change" %]
You need "editbugs" permissions to alter the '[% field FILTER html %]' field.
+[% ELSIF error == "mozchangefield_checkin_needed_tb_milestone" %]
+ [% title = "Target Milestone Required" %]
+ The [% keyword FILTER html %] keyword requires a
+ Target Milestone to be set.
+
[% END %]