Merge "Support to query groups by owner group name"
diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs
index fd57ff7..172bb97 100644
--- a/.settings/org.eclipse.jdt.core.prefs
+++ b/.settings/org.eclipse.jdt.core.prefs
@@ -113,290 +113,4 @@
 org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
 org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
 org.eclipse.jdt.core.compiler.processAnnotations=enabled
-org.eclipse.jdt.core.compiler.source=1.8
-org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_assignment=16
-org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
-org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_enum_constants=16
-org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
-org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0
-org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
-org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=80
-org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
-org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16
-org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
-org.eclipse.jdt.core.formatter.blank_lines_after_package=1
-org.eclipse.jdt.core.formatter.blank_lines_before_field=0
-org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
-org.eclipse.jdt.core.formatter.blank_lines_before_imports=0
-org.eclipse.jdt.core.formatter.blank_lines_before_member_type=0
-org.eclipse.jdt.core.formatter.blank_lines_before_method=1
-org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
-org.eclipse.jdt.core.formatter.blank_lines_before_package=0
-org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
-org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=2
-org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_lambda_body=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
-org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
-org.eclipse.jdt.core.formatter.comment.format_block_comments=true
-org.eclipse.jdt.core.formatter.comment.format_header=true
-org.eclipse.jdt.core.formatter.comment.format_html=true
-org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
-org.eclipse.jdt.core.formatter.comment.format_line_comments=true
-org.eclipse.jdt.core.formatter.comment.format_source_code=true
-org.eclipse.jdt.core.formatter.comment.indent_parameter_description=false
-org.eclipse.jdt.core.formatter.comment.indent_root_tags=true
-org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
-org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=do not insert
-org.eclipse.jdt.core.formatter.comment.line_length=80
-org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true
-org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true
-org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false
-org.eclipse.jdt.core.formatter.compact_else_if=true
-org.eclipse.jdt.core.formatter.continuation_indentation=2
-org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
-org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off
-org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on
-org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
-org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
-org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
-org.eclipse.jdt.core.formatter.indent_empty_lines=false
-org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
-org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
-org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
-org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=true
-org.eclipse.jdt.core.formatter.indentation.size=4
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_member=insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
-org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
-org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow=insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources=insert
-org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
-org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
-org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
-org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
-org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.join_lines_in_comments=true
-org.eclipse.jdt.core.formatter.join_wrapped_lines=true
-org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
-org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
-org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=true
-org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
-org.eclipse.jdt.core.formatter.lineSplit=80
-org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
-org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
-org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
-org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=3
-org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=false
-org.eclipse.jdt.core.formatter.tabulation.char=space
-org.eclipse.jdt.core.formatter.tabulation.size=2
-org.eclipse.jdt.core.formatter.use_on_off_tags=false
-org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false
-org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
-org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true
-org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true
-org.eclipse.jdt.core.javaFormatter=org.eclipse.jdt.core.defaultJavaFormatter
+org.eclipse.jdt.core.compiler.source=1.8
\ No newline at end of file
diff --git a/.settings/org.eclipse.jdt.ui.prefs b/.settings/org.eclipse.jdt.ui.prefs
index d990610..3d5f5f6 100644
--- a/.settings/org.eclipse.jdt.ui.prefs
+++ b/.settings/org.eclipse.jdt.ui.prefs
@@ -1,60 +1,5 @@
 eclipse.preferences.version=1
-editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
-formatter_profile=_Google Format
-formatter_settings_version=12
 org.eclipse.jdt.ui.ignorelowercasenames=true
-org.eclipse.jdt.ui.importorder=\#;com.google;com;dk;eu;junit;net;org;java;javax;
 org.eclipse.jdt.ui.ondemandthreshold=99
 org.eclipse.jdt.ui.staticondemandthreshold=99
 org.eclipse.jdt.ui.text.custom_code_templates=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?><templates/>
-sp_cleanup.add_default_serial_version_id=true
-sp_cleanup.add_generated_serial_version_id=false
-sp_cleanup.add_missing_annotations=false
-sp_cleanup.add_missing_deprecated_annotations=true
-sp_cleanup.add_missing_methods=false
-sp_cleanup.add_missing_nls_tags=false
-sp_cleanup.add_missing_override_annotations=true
-sp_cleanup.add_serial_version_id=false
-sp_cleanup.always_use_blocks=true
-sp_cleanup.always_use_parentheses_in_expressions=false
-sp_cleanup.always_use_this_for_non_static_field_access=false
-sp_cleanup.always_use_this_for_non_static_method_access=false
-sp_cleanup.convert_to_enhanced_for_loop=false
-sp_cleanup.correct_indentation=false
-sp_cleanup.format_source_code=false
-sp_cleanup.format_source_code_changes_only=false
-sp_cleanup.make_local_variable_final=true
-sp_cleanup.make_parameters_final=true
-sp_cleanup.make_private_fields_final=true
-sp_cleanup.make_type_abstract_if_missing_method=false
-sp_cleanup.make_variable_declarations_final=false
-sp_cleanup.never_use_blocks=false
-sp_cleanup.never_use_parentheses_in_expressions=true
-sp_cleanup.on_save_use_additional_actions=true
-sp_cleanup.organize_imports=false
-sp_cleanup.qualify_static_field_accesses_with_declaring_class=false
-sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
-sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
-sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
-sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
-sp_cleanup.remove_private_constructors=true
-sp_cleanup.remove_trailing_whitespaces=true
-sp_cleanup.remove_trailing_whitespaces_all=true
-sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
-sp_cleanup.remove_unnecessary_casts=false
-sp_cleanup.remove_unnecessary_nls_tags=false
-sp_cleanup.remove_unused_imports=false
-sp_cleanup.remove_unused_local_variables=false
-sp_cleanup.remove_unused_private_fields=true
-sp_cleanup.remove_unused_private_members=false
-sp_cleanup.remove_unused_private_methods=true
-sp_cleanup.remove_unused_private_types=true
-sp_cleanup.sort_members=false
-sp_cleanup.sort_members_all=false
-sp_cleanup.use_blocks=false
-sp_cleanup.use_blocks_only_for_return_and_throw=false
-sp_cleanup.use_parentheses_in_expressions=false
-sp_cleanup.use_this_for_non_static_field_access=false
-sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
-sp_cleanup.use_this_for_non_static_method_access=false
-sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true
diff --git a/Documentation/dev-bazel.txt b/Documentation/dev-bazel.txt
index c1cd0e8..4ef2a35 100644
--- a/Documentation/dev-bazel.txt
+++ b/Documentation/dev-bazel.txt
@@ -214,6 +214,18 @@
   bazel test --test_env=GERRIT_USE_SSH=NO //...
 ----
 
+To exclude tests that have been marked as flaky:
+
+----
+  bazel test --test_tag_filters=-flaky //...
+----
+
+To ignore cached test results:
+
+----
+  bazel test --cache_test_results=NO //...
+----
+
 == Dependencies
 
 Dependency JARs are normally downloaded as needed, but you can
diff --git a/Documentation/dev-contributing.txt b/Documentation/dev-contributing.txt
index d7882a5..435e316 100644
--- a/Documentation/dev-contributing.txt
+++ b/Documentation/dev-contributing.txt
@@ -144,7 +144,7 @@
 link:https://gerrit-review.googlesource.com/#/settings/http-password[HTTP
 Password tab of the user settings page].
 
-
+[[style]]
 === Style
 
 Gerrit generally follows the
diff --git a/Documentation/dev-eclipse.txt b/Documentation/dev-eclipse.txt
index 5ada1e2..39fa333 100644
--- a/Documentation/dev-eclipse.txt
+++ b/Documentation/dev-eclipse.txt
@@ -45,12 +45,16 @@
 [[Formatting]]
 == Code Formatter Settings
 
-Import `tools/GoogleFormat.xml` using Window -> Preferences ->
-Java -> Code Style -> Formatter -> Import...
-
-This will define the 'Google Format' profile, which the project
-settings prefer when formatting source code.
-
+To format source code, Gerrit uses the
+link:https://github.com/google/google-java-format[`google-java-format`]
+tool (version 1.3), which automatically formats code to follow the
+style guide. See link:dev-contributing.html#style[Code Style] for the
+instruction how to set up command line tool that uses this formatter.
+The Eclipse plugin is provided that allows to format with the same
+formatter from within the Eclipse IDE. See
+link:https://github.com/google/google-java-format#eclipse[Eclipse plugin]
+for details how to install it. It's important to use the same plugin version
+as the `google-java-format` script.
 
 == Site Initialization
 
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index 6a78d9f..b0e64a8 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -5152,6 +5152,8 @@
 Number of inserted lines.
 |`deletions`          ||
 Number of deleted lines.
+|`unresolved_comment_count`  |optional|
+Number of unresolved comments. Not set if the current change index doesn't have the data.
 |`_number`            ||The legacy numeric ID of the change.
 |`owner`              ||
 The owner of the change as an link:rest-api-accounts.html#account-info[
diff --git a/Documentation/user-review-ui.txt b/Documentation/user-review-ui.txt
index 4803d83..5bd8d46 100644
--- a/Documentation/user-review-ui.txt
+++ b/Documentation/user-review-ui.txt
@@ -271,12 +271,14 @@
 
 ** [[delete]]`Delete Change` / `Delete Revision`:
 +
-Deletes the draft change / the currently viewed draft patch set.
+Deletes the change / the currently viewed draft patch set.
 +
-The `Delete Change` / `Delete Revision` buttons are only available if a
-draft patch set is viewed and the user is the change owner or has the
-link:access-control.html#category_delete_drafts[Delete Drafts] access
-right assigned.
+For normal changes, the `Delete Change` button will only be available if the
+user is an administrator and the change hasn't been merged. For draft changes,
+the `Delete Change` / `Delete Revision` buttons will be available if the user
+is the change owner or has the
+link:access-control.html#category_delete_drafts[Delete Drafts] access right
+assigned.
 
 ** [[plugin-actions]]Further actions may be available if plugins are installed.
 
diff --git a/Documentation/user-search.txt b/Documentation/user-search.txt
index f1f1654..2b5702e 100644
--- a/Documentation/user-search.txt
+++ b/Documentation/user-search.txt
@@ -278,6 +278,10 @@
 +
 True if the change has inline edit created by the current user.
 
+has:unresolved::
++
+True if the change has unresolved comments.
+
 [[is]]
 [[is-starred]]
 is:starred::
@@ -417,6 +421,16 @@
 only applies to the top-level status; individual label statuses can be
 searched link:#labels[by label].
 
+[[unresolved]]
+unresolved:'RELATION''NUMBER'::
++
+True if the number of unresolved comments satisfies the given relation for the given number.
++
+For example, unresolved:>0 will be true for any change which has at least one unresolved
+comment while unresolved:0 will be true for any change which has all comments resolved.
++
+Valid relations are >=, >, <=, <, or no relation, which will match if the number of unresolved
+comments is exactly equal.
 
 == Argument Quoting
 
diff --git a/WORKSPACE b/WORKSPACE
index 986b4c2..1ffe850 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -696,10 +696,18 @@
     sha1 = "42a25dc3219429f0e5d060061f71acb49bf010a0",
 )
 
+TRUTH_VERS = "0.31"
+
 maven_jar(
     name = "truth",
-    artifact = "com.google.truth:truth:0.30",
-    sha1 = "9d591b5a66eda81f0b88cf1c748ab8853d99b18b",
+    artifact = "com.google.truth:truth:" + TRUTH_VERS,
+    sha1 = "1a926b0cb2879fd32efbb3716ee8bab040f4218b",
+)
+
+maven_jar(
+    name = "truth-java8-extension",
+    artifact = "com.google.truth.extensions:truth-java8-extension:" + TRUTH_VERS,
+    sha1 = "a7e80e631f2bf4ecc2b99ad1e33059eb0dcc6ea0",
 )
 
 maven_jar(
@@ -1098,8 +1106,8 @@
 bower_archive(
     name = "web-component-tester",
     package = "web-component-tester",
-    sha1 = "54556000c33d9ed7949aa546c1b4a1531491a5f0",
-    version = "4.2.2",
+    sha1 = "a4a9bc7815a22d143e8f8593e37b3c2028b8c20f",
+    version = "5.0.0",
 )
 
 # Bower component transitive dependencies.
diff --git a/gerrit-acceptance-framework/BUILD b/gerrit-acceptance-framework/BUILD
index 69f132b..db5a300 100644
--- a/gerrit-acceptance-framework/BUILD
+++ b/gerrit-acceptance-framework/BUILD
@@ -41,6 +41,7 @@
         "//gerrit-server:testutil",
         "//gerrit-server/src/main/prolog:common",
         "//lib:truth",
+        "//lib:truth-java8-extension",
         "//lib/auto:auto-value",
         "//lib/httpcomponents:fluent-hc",
         "//lib/httpcomponents:httpclient",
diff --git a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
index b7358d6..2cc64d8 100644
--- a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
+++ b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
@@ -696,6 +696,16 @@
         identifiedUserFactory.create(account.getId()));
   }
 
+  /**
+   * Enforce a new request context for the current API user.
+   *
+   * <p>This recreates the IdentifiedUser, hence everything which is cached in the IdentifiedUser is
+   * reloaded (e.g. the email addresses of the user).
+   */
+  protected Context resetCurrentApiUser() {
+    return atrScope.set(newRequestContext(atrScope.get().getSession().getAccount()));
+  }
+
   protected Context setApiUser(TestAccount account) {
     return atrScope.set(newRequestContext(account));
   }
diff --git a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/SshSession.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/SshSession.java
index 1f00248..f7369d7 100644
--- a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/SshSession.java
+++ b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/SshSession.java
@@ -111,4 +111,8 @@
     b.append(session.getPort());
     return b.toString();
   }
+
+  public TestAccount getAccount() {
+    return account;
+  }
 }
diff --git a/gerrit-acceptance-tests/BUILD b/gerrit-acceptance-tests/BUILD
index e7a6222..3154b1f 100644
--- a/gerrit-acceptance-tests/BUILD
+++ b/gerrit-acceptance-tests/BUILD
@@ -1,5 +1,6 @@
 java_library(
     name = "lib",
+    srcs = ["src/test/java/com/google/gerrit/acceptance/Dummy.java"],
     testonly = 1,
     visibility = ["//visibility:public"],
     exports = [
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/Dummy.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/Dummy.java
new file mode 100644
index 0000000..d910638
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/Dummy.java
@@ -0,0 +1,18 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance;
+
+public class Dummy {
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AccountIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AccountIT.java
index 11b452e..82aa576 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AccountIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AccountIT.java
@@ -386,9 +386,7 @@
       gApi.accounts().self().addEmail(input);
     }
 
-    // enforce a new request context so that emails that are cached in
-    // IdentifiedUser are reloaded
-    setApiUser(admin);
+    resetCurrentApiUser();
     assertThat(getEmails()).containsAllIn(emails);
   }
 
@@ -428,20 +426,67 @@
     input.noConfirmation = true;
     gApi.accounts().self().addEmail(input);
 
-    // enforce a new request context so that emails that are cached in
-    // IdentifiedUser are reloaded
-    setApiUser(admin);
+    resetCurrentApiUser();
     assertThat(getEmails()).contains(email);
 
     gApi.accounts().self().deleteEmail(input.email);
 
-    // enforce a new request context so that emails that are cached in
-    // IdentifiedUser are reloaded
-    setApiUser(admin);
+    resetCurrentApiUser();
     assertThat(getEmails()).doesNotContain(email);
   }
 
   @Test
+  public void deleteEmailFromCustomExternalIdSchemes() throws Exception {
+    String email = "foo.bar@example.com";
+    String extId1 = "foo:bar";
+    String extId2 = "foo:baz";
+    db.accountExternalIds()
+        .insert(
+            ImmutableList.of(
+                createExternalIdWithEmail(extId1, email),
+                createExternalIdWithEmail(extId2, email)));
+    accountCache.evict(admin.id);
+    assertThat(
+            gApi.accounts().self().getExternalIds().stream().map(e -> e.identity).collect(toSet()))
+        .containsAllOf(extId1, extId2);
+
+    resetCurrentApiUser();
+    assertThat(getEmails()).contains(email);
+
+    gApi.accounts().self().deleteEmail(email);
+
+    resetCurrentApiUser();
+    assertThat(getEmails()).doesNotContain(email);
+    assertThat(
+            gApi.accounts().self().getExternalIds().stream().map(e -> e.identity).collect(toSet()))
+        .containsNoneOf(extId1, extId2);
+  }
+
+  @Test
+  public void deleteEmailOfOtherUser() throws Exception {
+    String email = "foo.bar@example.com";
+    EmailInput input = new EmailInput();
+    input.email = email;
+    input.noConfirmation = true;
+    gApi.accounts().id(user.id.get()).addEmail(input);
+
+    setApiUser(user);
+    assertThat(getEmails()).contains(email);
+
+    // admin can delete email of user
+    setApiUser(admin);
+    gApi.accounts().id(user.id.get()).deleteEmail(email);
+
+    setApiUser(user);
+    assertThat(getEmails()).doesNotContain(email);
+
+    // user cannot delete email of admin
+    exception.expect(AuthException.class);
+    exception.expectMessage("not allowed to delete email address");
+    gApi.accounts().id(admin.id.get()).deleteEmail(admin.email);
+  }
+
+  @Test
   public void putStatus() throws Exception {
     List<String> statuses = ImmutableList.of("OOO", "Busy");
     AccountInfo info;
@@ -886,4 +931,10 @@
   private Set<String> getEmails() throws RestApiException {
     return gApi.accounts().self().getEmails().stream().map(e -> e.email).collect(toSet());
   }
+
+  private AccountExternalId createExternalIdWithEmail(String id, String email) {
+    AccountExternalId extId = new AccountExternalId(admin.id, new AccountExternalId.Key(id));
+    extId.setEmailAddress(email);
+    return extId;
+  }
 }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java
index 998abbf..df82e21 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.acceptance.api.change;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth8.assertThat;
 import static com.google.common.truth.TruthJUnit.assume;
 import static com.google.gerrit.acceptance.GitUtil.assertPushOk;
 import static com.google.gerrit.acceptance.GitUtil.pushHead;
@@ -31,9 +32,11 @@
 import static com.google.gerrit.server.project.Util.value;
 import static java.util.concurrent.TimeUnit.SECONDS;
 import static java.util.stream.Collectors.toList;
+import static java.util.stream.Collectors.toSet;
 import static org.junit.Assert.fail;
 
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
@@ -57,6 +60,7 @@
 import com.google.gerrit.extensions.api.changes.RebaseInput;
 import com.google.gerrit.extensions.api.changes.RecipientType;
 import com.google.gerrit.extensions.api.changes.ReviewInput;
+import com.google.gerrit.extensions.api.changes.ReviewInput.DraftHandling;
 import com.google.gerrit.extensions.api.changes.RevisionApi;
 import com.google.gerrit.extensions.api.projects.BranchInput;
 import com.google.gerrit.extensions.client.ChangeKind;
@@ -114,6 +118,8 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
 import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
 import org.eclipse.jgit.junit.TestRepository;
 import org.eclipse.jgit.lib.Constants;
@@ -945,17 +951,41 @@
   }
 
   @Test
-  public void implicitlyCcOnNonVotingReview() throws Exception {
+  public void implicitlyCcOnNonVotingReviewPgStyle() throws Exception {
     PushOneCommit.Result r = createChange();
     setApiUser(user);
-    gApi.changes().id(r.getChangeId()).revision(r.getCommit().name()).review(new ReviewInput());
+    assertThat(getReviewerState(r.getChangeId(), user.id)).isEmpty();
 
-    ChangeInfo c = gApi.changes().id(r.getChangeId()).get();
-    // If we're not reading from NoteDb, then the CCed user will be returned
-    // in the REVIEWER state.
-    ReviewerState state = notesMigration.readChanges() ? CC : REVIEWER;
-    assertThat(c.reviewers.get(state).stream().map(ai -> ai._accountId).collect(toList()))
-        .containsExactly(user.id.get());
+    // Exact request format made by PG UI at ddc6b7160fe416fed9e7e3180489d44c82fd64f8.
+    ReviewInput in = new ReviewInput();
+    in.drafts = DraftHandling.PUBLISH_ALL_REVISIONS;
+    in.labels = ImmutableMap.of();
+    in.message = "comment";
+    in.reviewers = ImmutableList.of();
+    gApi.changes().id(r.getChangeId()).revision(r.getCommit().name()).review(in);
+
+    // If we're not reading from NoteDb, then the CCed user will be returned in the REVIEWER state.
+    assertThat(getReviewerState(r.getChangeId(), user.id))
+        .hasValue(notesMigration.readChanges() ? CC : REVIEWER);
+  }
+
+  @Test
+  public void implicitlyCcOnNonVotingReviewGwtStyle() throws Exception {
+    PushOneCommit.Result r = createChange();
+    setApiUser(user);
+    assertThat(getReviewerState(r.getChangeId(), user.id)).isEmpty();
+
+    // Exact request format made by GWT UI at ddc6b7160fe416fed9e7e3180489d44c82fd64f8.
+    ReviewInput in = new ReviewInput();
+    in.labels = ImmutableMap.of("Code-Review", (short) 0);
+    in.strictLabels = true;
+    in.drafts = DraftHandling.PUBLISH_ALL_REVISIONS;
+    in.message = "comment";
+    gApi.changes().id(r.getChangeId()).revision(r.getCommit().name()).review(in);
+
+    // If we're not reading from NoteDb, then the CCed user will be returned in the REVIEWER state.
+    assertThat(getReviewerState(r.getChangeId(), user.id))
+        .hasValue(notesMigration.readChanges() ? CC : REVIEWER);
   }
 
   @Test
@@ -2290,6 +2320,20 @@
     return changeResourceFactory.create(ctls.get(0));
   }
 
+  private Optional<ReviewerState> getReviewerState(String changeId, Account.Id accountId)
+      throws Exception {
+    ChangeInfo c = gApi.changes().id(changeId).get(EnumSet.of(ListChangesOption.DETAILED_LABELS));
+    Set<ReviewerState> states =
+        c.reviewers
+            .entrySet()
+            .stream()
+            .filter(e -> e.getValue().stream().anyMatch(a -> a._accountId == accountId.get()))
+            .map(e -> e.getKey())
+            .collect(toSet());
+    assertThat(states.size()).named(states.toString()).isAtMost(1);
+    return states.stream().findFirst();
+  }
+
   private void setChangeStatus(Change.Id id, Change.Status newStatus) throws Exception {
     try (BatchUpdate batchUpdate =
         updateFactory.create(db, project, atrScope.get().getUser(), TimeUtil.nowTs())) {
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/RobotCommentsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/RobotCommentsIT.java
index 283f14b..faa21cf 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/RobotCommentsIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/RobotCommentsIT.java
@@ -17,14 +17,17 @@
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.TruthJUnit.assume;
 import static com.google.gerrit.acceptance.PushOneCommit.FILE_NAME;
+import static com.google.gerrit.acceptance.PushOneCommit.SUBJECT;
 import static com.google.gerrit.extensions.common.RobotCommentInfoSubject.assertThatList;
 
 import com.google.common.collect.Iterables;
 import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.AcceptanceTestRequestScope;
 import com.google.gerrit.acceptance.PushOneCommit;
 import com.google.gerrit.extensions.api.changes.ReviewInput;
 import com.google.gerrit.extensions.api.changes.ReviewInput.RobotCommentInput;
 import com.google.gerrit.extensions.client.Comment;
+import com.google.gerrit.extensions.common.ChangeInfo;
 import com.google.gerrit.extensions.common.FixReplacementInfo;
 import com.google.gerrit.extensions.common.FixSuggestionInfo;
 import com.google.gerrit.extensions.common.RobotCommentInfo;
@@ -364,6 +367,31 @@
     gApi.changes().id(changeId).current().review(reviewInput);
   }
 
+  @Test
+  public void queryChangesWithUnresolvedCommentCount() throws Exception {
+    assume().that(notesMigration.enabled()).isTrue();
+
+    PushOneCommit.Result r1 = createChange();
+    PushOneCommit.Result r2 =
+        pushFactory
+            .create(
+                db, admin.getIdent(), testRepo, SUBJECT, FILE_NAME, "new content", r1.getChangeId())
+            .to("refs/for/master");
+
+    addRobotComment(r2.getChangeId(), createRobotCommentInputWithMandatoryFields());
+
+    AcceptanceTestRequestScope.Context ctx = disableDb();
+    try {
+      ChangeInfo result = Iterables.getOnlyElement(query(r2.getChangeId()));
+      // currently, we create all robot comments as 'resolved' by default.
+      // if we allow users to resolve a robot comment, then this test should
+      // be modified.
+      assertThat(result.unresolvedCommentCount).isEqualTo(0);
+    } finally {
+      enableDb(ctx);
+    }
+  }
+
   private RobotCommentInput createRobotCommentInputWithMandatoryFields() {
     RobotCommentInput in = new RobotCommentInput();
     in.robotId = "happyRobot";
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractPushForReview.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractPushForReview.java
index 72146ab..a4b2209 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractPushForReview.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractPushForReview.java
@@ -866,6 +866,16 @@
 
   @Test
   public void pushAFewChanges() throws Exception {
+    testPushAFewChanges();
+  }
+
+  @Test
+  public void pushAFewChangesWithCreateNewChangeForAllNotInTarget() throws Exception {
+    enableCreateNewChangeForAllNotInTarget();
+    testPushAFewChanges();
+  }
+
+  private void testPushAFewChanges() throws Exception {
     int n = 10;
     String r = "refs/for/master";
     ObjectId initialHead = testRepo.getRepository().resolve("HEAD");
@@ -991,11 +1001,75 @@
     pushForReviewRejected(testRepo, "invalid Change-Id line format in commit message footer");
   }
 
+  @Test
+  public void pushCommitWithSameChangeIdAsPredecessorChange() throws Exception {
+    PushOneCommit push =
+        pushFactory.create(
+            db, admin.getIdent(), testRepo, PushOneCommit.SUBJECT, "a.txt", "content");
+    PushOneCommit.Result r = push.to("refs/for/master");
+    r.assertOkStatus();
+    RevCommit commitChange1 = r.getCommit();
+
+    createCommit(testRepo, commitChange1.getFullMessage());
+
+    pushForReviewRejected(
+        testRepo,
+        "same Change-Id in multiple changes.\n"
+            + "Squash the commits with the same Change-Id or ensure Change-Ids are unique for each"
+            + " commit");
+
+    ProjectConfig config = projectCache.checkedGet(project).getConfig();
+    config.getProject().setRequireChangeID(InheritableBoolean.FALSE);
+    saveProjectConfig(project, config);
+
+    pushForReviewRejected(
+        testRepo,
+        "same Change-Id in multiple changes.\n"
+            + "Squash the commits with the same Change-Id or ensure Change-Ids are unique for each"
+            + " commit");
+  }
+
+  @Test
+  public void pushTwoCommitWithSameChangeId() throws Exception {
+    RevCommit commitChange1 = createCommitWithChangeId(testRepo, "some change");
+
+    createCommit(testRepo, commitChange1.getFullMessage());
+
+    pushForReviewRejected(
+        testRepo,
+        "same Change-Id in multiple changes.\n"
+            + "Squash the commits with the same Change-Id or ensure Change-Ids are unique for each"
+            + " commit");
+
+    ProjectConfig config = projectCache.checkedGet(project).getConfig();
+    config.getProject().setRequireChangeID(InheritableBoolean.FALSE);
+    saveProjectConfig(project, config);
+
+    pushForReviewRejected(
+        testRepo,
+        "same Change-Id in multiple changes.\n"
+            + "Squash the commits with the same Change-Id or ensure Change-Ids are unique for each"
+            + " commit");
+  }
+
   private static RevCommit createCommit(TestRepository<?> testRepo, String message)
       throws Exception {
     return testRepo.branch("HEAD").commit().message(message).add("a.txt", "content").create();
   }
 
+  private static RevCommit createCommitWithChangeId(TestRepository<?> testRepo, String message)
+      throws Exception {
+    RevCommit c =
+        testRepo
+            .branch("HEAD")
+            .commit()
+            .message(message)
+            .insertChangeId()
+            .add("a.txt", "content")
+            .create();
+    return testRepo.getRevWalk().parseCommit(c);
+  }
+
   @Test
   public void cantAutoCloseChangeAlreadyMergedToBranch() throws Exception {
     PushOneCommit.Result r1 = createChange();
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/EmailIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/EmailIT.java
index f9d3357..186d625 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/EmailIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/EmailIT.java
@@ -42,7 +42,7 @@
     String email = "foo.bar2@example.com";
     assertThat(getEmails()).doesNotContain(email);
 
-    createEmail(email.replaceAll("@", "%40"));
+    createEmail(email.replace("@", "%40"));
     assertThat(getEmails()).contains(email);
   }
 
@@ -67,8 +67,7 @@
     createEmail(email);
     assertThat(getEmails()).contains(email);
 
-    RestResponse r =
-        adminRestSession.delete("/accounts/self/emails/" + email.replaceAll("@", "%40"));
+    RestResponse r = adminRestSession.delete("/accounts/self/emails/" + email.replace("@", "%40"));
     r.assertNoContent();
     assertThat(getEmails()).doesNotContain(email);
   }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/KillTaskIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/KillTaskIT.java
index ece30e9..f05ecce 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/KillTaskIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/KillTaskIT.java
@@ -15,12 +15,15 @@
 package com.google.gerrit.acceptance.rest.config;
 
 import static com.google.common.truth.Truth.assertThat;
+import static java.util.stream.Collectors.toSet;
 
 import com.google.gerrit.acceptance.AbstractDaemonTest;
 import com.google.gerrit.acceptance.RestResponse;
 import com.google.gerrit.server.config.ListTasks.TaskInfo;
 import com.google.gson.reflect.TypeToken;
 import java.util.List;
+import java.util.Optional;
+import java.util.Set;
 import org.junit.Test;
 
 public class KillTaskIT extends AbstractDaemonTest {
@@ -30,17 +33,24 @@
     List<TaskInfo> result =
         newGson().fromJson(r.getReader(), new TypeToken<List<TaskInfo>>() {}.getType());
     r.consume();
-    int taskCount = result.size();
-    assertThat(taskCount).isGreaterThan(0);
 
-    r = adminRestSession.delete("/config/server/tasks/" + result.get(0).id);
+    Optional<String> id =
+        result
+            .stream()
+            .filter(t -> "Log File Compressor".equals(t.command))
+            .map(t -> t.id)
+            .findFirst();
+    assertThat(id.isPresent()).isTrue();
+
+    r = adminRestSession.delete("/config/server/tasks/" + id.get());
     r.assertNoContent();
     r.consume();
 
     r = adminRestSession.get("/config/server/tasks/");
     result = newGson().fromJson(r.getReader(), new TypeToken<List<TaskInfo>>() {}.getType());
     r.consume();
-    assertThat(result).hasSize(taskCount - 1);
+    Set<String> ids = result.stream().map(t -> t.id).collect(toSet());
+    assertThat(ids).doesNotContain(id.get());
   }
 
   private void killTask_NotFound() throws Exception {
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/CommentsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/CommentsIT.java
index 2fa0f4d..7a84e6d 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/CommentsIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/CommentsIT.java
@@ -23,6 +23,7 @@
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.AcceptanceTestRequestScope;
 import com.google.gerrit.acceptance.NoHttpd;
 import com.google.gerrit.acceptance.PushOneCommit;
 import com.google.gerrit.extensions.api.changes.DraftInput;
@@ -31,6 +32,7 @@
 import com.google.gerrit.extensions.api.changes.ReviewInput.DraftHandling;
 import com.google.gerrit.extensions.client.Comment;
 import com.google.gerrit.extensions.client.Side;
+import com.google.gerrit.extensions.common.ChangeInfo;
 import com.google.gerrit.extensions.common.CommentInfo;
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.IdString;
@@ -401,7 +403,7 @@
     addComment(r1, "nit: trailing whitespace");
     Map<String, List<CommentInfo>> result = getPublishedComments(changeId, revId);
     assertThat(result.get(FILE_NAME)).hasSize(2);
-    addComment(r1, "nit: trailing whitespace", true);
+    addComment(r1, "nit: trailing whitespace", true, false);
     result = getPublishedComments(changeId, revId);
     assertThat(result.get(FILE_NAME)).hasSize(2);
 
@@ -411,7 +413,7 @@
             .to("refs/for/master");
     changeId = r2.getChangeId();
     revId = r2.getCommit().getName();
-    addComment(r2, "nit: trailing whitespace", true);
+    addComment(r2, "nit: trailing whitespace", true, false);
     result = getPublishedComments(changeId, revId);
     assertThat(result.get(FILE_NAME)).hasSize(1);
   }
@@ -694,6 +696,30 @@
     assertThat(drafts.get(0).tag).isEqualTo("tag2");
   }
 
+  @Test
+  public void queryChangesWithUnresolvedCommentCount() throws Exception {
+    PushOneCommit.Result r1 = createChange();
+
+    addComment(r1, "comment 1", false, true);
+    addComment(r1, "nit: trailing whitespace", false, null);
+
+    PushOneCommit.Result r2 =
+        pushFactory
+            .create(
+                db, admin.getIdent(), testRepo, SUBJECT, FILE_NAME, "new cntent", r1.getChangeId())
+            .to("refs/for/master");
+
+    addComment(r2, "typo: content", false, false);
+
+    AcceptanceTestRequestScope.Context ctx = disableDb();
+    try {
+      ChangeInfo result = Iterables.getOnlyElement(query(r2.getChangeId()));
+      assertThat(result.unresolvedCommentCount).isEqualTo(1);
+    } finally {
+      enableDb(ctx);
+    }
+  }
+
   private static String extractComments(String msg) {
     // Extract lines between start "....." and end "-- ".
     Pattern p = Pattern.compile(".*[.]{5}\n+(.*)\\n+-- \n.*", Pattern.DOTALL);
@@ -709,15 +735,17 @@
   }
 
   private void addComment(PushOneCommit.Result r, String message) throws Exception {
-    addComment(r, message, false);
+    addComment(r, message, false, false);
   }
 
-  private void addComment(PushOneCommit.Result r, String message, boolean omitDuplicateComments)
+  private void addComment(
+      PushOneCommit.Result r, String message, boolean omitDuplicateComments, Boolean unresolved)
       throws Exception {
     CommentInput c = new CommentInput();
     c.line = 1;
     c.message = message;
     c.path = FILE_NAME;
+    c.unresolved = unresolved;
     ReviewInput in = newInput(c);
     in.omitDuplicateComments = omitDuplicateComments;
     gApi.changes().id(r.getChangeId()).revision(r.getCommit().name()).review(in);
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java
index dc2668d..83bb3af 100644
--- a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java
+++ b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java
@@ -353,6 +353,7 @@
           ChangeField.STORED_SUBMIT_RECORD_LENIENT.getName(),
           ChangeField.SUBMIT_RULE_OPTIONS_LENIENT,
           cd);
+      decodeUnresolvedCommentCount(source, ChangeField.UNRESOLVED_COMMENT_COUNT.getName(), cd);
 
       if (source.get(ChangeField.REF_STATE.getName()) != null) {
         JsonArray refStates = source.get(ChangeField.REF_STATE.getName()).getAsJsonArray();
@@ -381,5 +382,13 @@
           opts,
           out);
     }
+
+    private void decodeUnresolvedCommentCount(JsonObject doc, String fieldName, ChangeData out) {
+      JsonElement count = doc.get(fieldName);
+      if (count == null) {
+        return;
+      }
+      out.setUnresolvedCommentCount(count.getAsInt());
+    }
   }
 }
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ChangeInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ChangeInfo.java
index 7061f31..3803714 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ChangeInfo.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ChangeInfo.java
@@ -43,6 +43,7 @@
   public Boolean submittable;
   public Integer insertions;
   public Integer deletions;
+  public Integer unresolvedCommentCount;
 
   public int _number;
 
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchSuggestOracle.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchSuggestOracle.java
index cbd12ea..165d0ca 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchSuggestOracle.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchSuggestOracle.java
@@ -118,6 +118,7 @@
     suggestions.add("has:edit");
     suggestions.add("has:star");
     suggestions.add("has:stars");
+    suggestions.add("has:unresolved");
     suggestions.add("star:");
 
     suggestions.add("is:");
@@ -148,6 +149,8 @@
     suggestions.add("delta:");
     suggestions.add("size:");
 
+    suggestions.add("unresolved:");
+
     if (Gerrit.isNoteDbEnabled()) {
       suggestions.add("hashtag:");
     }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/ExternalIdInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/ExternalIdInfo.java
index 8d5cd68..4ac0716 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/ExternalIdInfo.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/ExternalIdInfo.java
@@ -57,8 +57,6 @@
       // Describe a mailto address as just its email address,
       // which is already shown in the email address field.
       return "";
-    } else if (isScheme("https://www.google.com/accounts/o8/id")) {
-      return OpenIdUtil.C.nameGoogle();
     } else if (isScheme(OpenIdUrls.URL_LAUNCHPAD)) {
       return OpenIdUtil.C.nameLaunchpad();
     } else if (isScheme(OpenIdUrls.URL_YAHOO)) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/auth/openid/OpenIdConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/auth/openid/OpenIdConstants.java
index 2c21b74..a0eaef7 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/auth/openid/OpenIdConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/auth/openid/OpenIdConstants.java
@@ -17,8 +17,6 @@
 import com.google.gwt.i18n.client.Constants;
 
 public interface OpenIdConstants extends Constants {
-  String nameGoogle();
-
   String nameLaunchpad();
 
   String nameYahoo();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/auth/openid/OpenIdConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/auth/openid/OpenIdConstants.properties
index 08ddf38..d6e8de6 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/auth/openid/OpenIdConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/auth/openid/OpenIdConstants.properties
@@ -1,3 +1,2 @@
-nameGoogle = Google Account
 nameLaunchpad = Launchpad ID
 nameYahoo = Yahoo! ID
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndex.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndex.java
index 5477b31..ba5780e 100644
--- a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndex.java
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndex.java
@@ -127,6 +127,8 @@
       ChangeField.STORED_SUBMIT_RECORD_LENIENT.getName();
   private static final String SUBMIT_RECORD_STRICT_FIELD =
       ChangeField.STORED_SUBMIT_RECORD_STRICT.getName();
+  private static final String UNRESOLVED_COMMENT_COUNT_FIELD =
+      ChangeField.UNRESOLVED_COMMENT_COUNT.getName();
 
   static Term idTerm(ChangeData cd) {
     return QueryBuilder.intTerm(LEGACY_ID.getName(), cd.getId().get());
@@ -467,6 +469,8 @@
     if (fields.contains(REF_STATE_PATTERN_FIELD)) {
       decodeRefStatePatterns(doc, cd);
     }
+
+    decodeUnresolvedCommentCount(doc, cd);
     return cd;
   }
 
@@ -568,6 +572,14 @@
     cd.setRefStatePatterns(copyAsBytes(doc.get(REF_STATE_PATTERN_FIELD)));
   }
 
+  private void decodeUnresolvedCommentCount(
+      ListMultimap<String, IndexableField> doc, ChangeData cd) {
+    IndexableField f = Iterables.getFirst(doc.get(UNRESOLVED_COMMENT_COUNT_FIELD), null);
+    if (f != null && f.numericValue() != null) {
+      cd.setUnresolvedCommentCount(f.numericValue().intValue());
+    }
+  }
+
   private static <T> List<T> decodeProtos(
       ListMultimap<String, IndexableField> doc, String fieldName, ProtobufCodec<T> codec) {
     Collection<IndexableField> fields = doc.get(fieldName);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteEmail.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteEmail.java
index 96c4b8d..8541cf8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteEmail.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteEmail.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.server.account;
 
+import static java.util.stream.Collectors.toSet;
+
 import com.google.gerrit.extensions.client.AccountFieldName;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
@@ -31,6 +33,7 @@
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
 import java.io.IOException;
+import java.util.Set;
 
 @Singleton
 public class DeleteEmail implements RestModifyView<AccountResource.Email, Input> {
@@ -69,13 +72,26 @@
     if (!realm.allowsEdit(AccountFieldName.REGISTER_NEW_EMAIL)) {
       throw new MethodNotAllowedException("realm does not allow deleting emails");
     }
-    AccountExternalId.Key key = new AccountExternalId.Key(AccountExternalId.SCHEME_MAILTO, email);
-    AccountExternalId extId = dbProvider.get().accountExternalIds().get(key);
-    if (extId == null) {
+
+    Set<AccountExternalId> extIds =
+        dbProvider
+            .get()
+            .accountExternalIds()
+            .byAccount(user.getAccountId())
+            .toList()
+            .stream()
+            .filter(e -> email.equals(e.getEmailAddress()))
+            .collect(toSet());
+    if (extIds.isEmpty()) {
       throw new ResourceNotFoundException(email);
     }
+
     try {
-      accountManager.unlink(user.getAccountId(), AuthRequest.forEmail(email));
+      for (AccountExternalId extId : extIds) {
+        AuthRequest authRequest = new AuthRequest(extId.getKey().get());
+        authRequest.setEmailAddress(email);
+        accountManager.unlink(user.getAccountId(), authRequest);
+      }
     } catch (AccountException e) {
       throw new ResourceConflictException(e.getMessage());
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
index 8e1ab10..3b765ef 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
@@ -477,9 +477,10 @@
     out.created = in.getCreatedOn();
     out.updated = in.getLastUpdatedOn();
     out._number = in.getId().get();
+    out.unresolvedCommentCount = cd.unresolvedCommentCount();
 
     if (user.isIdentifiedUser()) {
-      Collection<String> stars = cd.stars().get(user.getAccountId());
+      Collection<String> stars = cd.stars(user.getAccountId());
       out.starred = stars.contains(StarredChangesUtil.DEFAULT_LABEL) ? true : null;
       if (!stars.isEmpty()) {
         out.stars = stars;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
index 790c241..d0c0e29 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
@@ -244,7 +244,10 @@
         batchUpdateFactory.create(
             db.get(), revision.getChange().getProject(), revision.getUser(), ts)) {
       Account.Id id = bu.getUser().getAccountId();
-      boolean ccOrReviewer = input.labels != null && !input.labels.isEmpty();
+      boolean ccOrReviewer = false;
+      if (input.labels != null && !input.labels.isEmpty()) {
+        ccOrReviewer = input.labels.values().stream().filter(v -> v != 0).findFirst().isPresent();
+      }
 
       if (!ccOrReviewer) {
         // Check if user was already CCed or reviewing prior to this review.
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeField.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeField.java
index 4747e1a..e2ca3dd 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeField.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeField.java
@@ -547,6 +547,16 @@
         }
       };
 
+  /** Number of unresolved comments of the change. */
+  public static final FieldDef<ChangeData, Integer> UNRESOLVED_COMMENT_COUNT =
+      new FieldDef.Single<ChangeData, Integer>(
+          ChangeQueryBuilder.FIELD_UNRESOLVED_COMMENT_COUNT, FieldType.INTEGER_RANGE, true) {
+        @Override
+        public Integer get(ChangeData input, FillArgs args) throws OrmException {
+          return input.unresolvedCommentCount();
+        }
+      };
+
   /** Whether the change is mergeable. */
   public static final FieldDef<ChangeData, String> MERGEABLE =
       new FieldDef.Single<ChangeData, String>(
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java
index 76a9bc9..a00dfe2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java
@@ -85,7 +85,9 @@
   static final Schema<ChangeData> V36 =
       schema(V35, ChangeField.REF_STATE, ChangeField.REF_STATE_PATTERN);
 
-  static final Schema<ChangeData> V37 = schema(V36);
+  @Deprecated static final Schema<ChangeData> V37 = schema(V36);
+
+  static final Schema<ChangeData> V38 = schema(V37, ChangeField.UNRESOLVED_COMMENT_COUNT);
 
   public static final String NAME = "changes";
   public static final ChangeSchemaDefinitions INSTANCE = new ChangeSchemaDefinitions();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/HtmlParser.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/HtmlParser.java
index 20e163e..f282c2d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/HtmlParser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/HtmlParser.java
@@ -90,12 +90,13 @@
           && elementName.equals("div")
           && !e.className().startsWith("gmail")) {
         // This is a comment typed by the user
-        String content = e.ownText().trim();
+        // Replace non-breaking spaces and trim string
+        String content = e.ownText().replace('\u00a0', ' ').trim();
         if (!Strings.isNullOrEmpty(content)) {
           if (lastEncounteredComment == null && lastEncounteredFileName == null) {
             // Remove quotation line, email signature and
             // "Sent from my xyz device"
-            content = ParserUtil.trimQuotationLine(content);
+            content = ParserUtil.trimQuotation(content);
             // TODO(hiesel) Add more sanitizer
             if (!Strings.isNullOrEmpty(content)) {
               parsedComments.add(
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/ParserUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/ParserUtil.java
index 72eb18a..bfead94 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/ParserUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/ParserUtil.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.server.mail.receive;
 
 import com.google.gerrit.reviewdb.client.Comment;
+import java.util.StringJoiner;
 import java.util.regex.Pattern;
 
 public class ParserUtil {
@@ -24,39 +25,44 @@
               + "(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})");
 
   /**
-   * Trims the quotation line that email clients add Example: On Sun, Nov 20, 2016 at 10:33 PM,
-   * <gerrit@hiesel.it> wrote:
+   * Trims the quotation that email clients add Example: On Sun, Nov 20, 2016 at 10:33 PM,
+   * <gerrit@gerritcodereview.com> wrote:
    *
    * @param comment Comment parsed from an email.
    * @return Trimmed comment.
    */
-  public static String trimQuotationLine(String comment) {
-    // Identifying the quotation line is hard, as it can be in any language.
-    // We identify this line by it's characteristics: It usually contains a
-    // valid email address, some digits for the date in groups of 1-4 in a row
-    // as well as some characters.
-    StringBuilder b = new StringBuilder();
-    for (String line : comment.split("\n")) {
-      // Count occurrences of digit groups
-      int numConsecutiveDigits = 0;
-      int maxConsecutiveDigits = 0;
-      int numDigitGroups = 0;
-      for (char c : line.toCharArray()) {
-        if (c >= '0' && c <= '9') {
-          numConsecutiveDigits++;
-        } else if (numConsecutiveDigits > 0) {
-          maxConsecutiveDigits = Integer.max(maxConsecutiveDigits, numConsecutiveDigits);
-          numConsecutiveDigits = 0;
-          numDigitGroups++;
-        }
+  public static String trimQuotation(String comment) {
+    StringJoiner j = new StringJoiner("\n");
+    String[] lines = comment.split("\n");
+    for (int i = 0; i < lines.length - 2; i++) {
+      j.add(lines[i]);
+    }
+
+    // Check if the last line contains the full quotation pattern (date + email)
+    String lastLine = lines[lines.length - 1];
+    if (containsQuotationPattern(lastLine)) {
+      if (lines.length > 1) {
+        j.add(lines[lines.length - 2]);
       }
-      if (numDigitGroups < 4
-          || maxConsecutiveDigits > 4
-          || !SIMPLE_EMAIL_PATTERN.matcher(line).find()) {
-        b.append(line);
+      return j.toString().trim();
+    }
+
+    // Check if the second last line + the last line contain the full quotation pattern. This is
+    // necessary, as the quotation line can be split across the last two lines if it gets too long.
+    if (lines.length > 1) {
+      String lastLines = lines[lines.length - 2] + lastLine;
+      if (containsQuotationPattern(lastLines)) {
+        return j.toString().trim();
       }
     }
-    return b.toString().trim();
+
+    // Add the last two lines
+    if (lines.length > 1) {
+      j.add(lines[lines.length - 2]);
+    }
+    j.add(lines[lines.length - 1]);
+
+    return j.toString().trim();
   }
 
   /** Check if string is an inline comment url on a patch set or the base */
@@ -69,4 +75,31 @@
   public static String filePath(String changeUrl, Comment comment) {
     return changeUrl + "/" + comment.key.patchSetId + "/" + comment.key.filename;
   }
+
+  private static boolean containsQuotationPattern(String s) {
+    // Identifying the quotation line is hard, as it can be in any language.
+    // We identify this line by it's characteristics: It usually contains a
+    // valid email address, some digits for the date in groups of 1-4 in a row
+    // as well as some characters.
+
+    // Count occurrences of digit groups
+    int numConsecutiveDigits = 0;
+    int maxConsecutiveDigits = 0;
+    int numDigitGroups = 0;
+    for (char c : s.toCharArray()) {
+      if (c >= '0' && c <= '9') {
+        numConsecutiveDigits++;
+      } else if (numConsecutiveDigits > 0) {
+        maxConsecutiveDigits = Integer.max(maxConsecutiveDigits, numConsecutiveDigits);
+        numConsecutiveDigits = 0;
+        numDigitGroups++;
+      }
+    }
+    if (numDigitGroups < 4 || maxConsecutiveDigits > 4) {
+      return false;
+    }
+
+    // Check if the string contains an email address
+    return SIMPLE_EMAIL_PATTERN.matcher(s).find();
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/TextParser.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/TextParser.java
index a752e89..fa33cc6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/TextParser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/TextParser.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.server.mail.receive;
 
+import com.google.common.base.Strings;
 import com.google.common.collect.Iterators;
 import com.google.common.collect.PeekingIterator;
 import com.google.gerrit.reviewdb.client.Comment;
@@ -64,12 +65,21 @@
     String lastEncounteredFileName = null;
     Comment lastEncounteredComment = null;
     for (String line : lines) {
+      if (line.equals(">")) {
+        // Skip empty lines
+        continue;
+      }
       if (line.startsWith("> ")) {
         line = line.substring("> ".length()).trim();
         // This is not a comment, try to advance the file/comment pointers and
         // add previous comment to list if applicable
         if (currentComment != null) {
-          parsedComments.add(currentComment);
+          if (currentComment.type == MailComment.CommentType.CHANGE_MESSAGE) {
+            currentComment.message = ParserUtil.trimQuotation(currentComment.message);
+          }
+          if (!Strings.isNullOrEmpty(currentComment.message)) {
+            parsedComments.add(currentComment);
+          }
           currentComment = null;
         }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
index 2263873..84af7be 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
@@ -24,6 +24,8 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableListMultimap;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSortedSet;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.ListMultimap;
 import com.google.common.collect.Lists;
@@ -39,6 +41,7 @@
 import com.google.gerrit.reviewdb.client.PatchSetApproval;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.reviewdb.client.RobotComment;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.AnonymousUser;
 import com.google.gerrit.server.ApprovalsUtil;
@@ -81,6 +84,7 @@
 import java.util.Set;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
 import org.eclipse.jgit.errors.MissingObjectException;
 import org.eclipse.jgit.errors.RepositoryNotFoundException;
@@ -332,6 +336,7 @@
   private Map<Integer, List<String>> files;
   private Map<Integer, Optional<DiffSummary>> diffSummaries;
   private Collection<Comment> publishedComments;
+  private Collection<RobotComment> robotComments;
   private CurrentUser visibleTo;
   private ChangeControl changeControl;
   private List<ChangeMessage> messages;
@@ -342,13 +347,14 @@
   private Map<Account.Id, Ref> editsByUser;
   private Set<Account.Id> reviewedBy;
   private Map<Account.Id, Ref> draftsByUser;
-  @Deprecated private Set<Account.Id> starredByUser;
   private ImmutableListMultimap<Account.Id, String> stars;
+  private StarsOf starsOf;
   private ImmutableMap<Account.Id, StarRef> starRefs;
   private ReviewerSet reviewers;
   private List<ReviewerStatusUpdate> reviewerUpdates;
   private PersonIdent author;
   private PersonIdent committer;
+  private Integer unresolvedCommentCount;
 
   private ImmutableList<byte[]> refStates;
   private ImmutableList<byte[]> refStatePatterns;
@@ -975,6 +981,34 @@
     return publishedComments;
   }
 
+  public Collection<RobotComment> robotComments() throws OrmException {
+    if (robotComments == null) {
+      if (!lazyLoad) {
+        return Collections.emptyList();
+      }
+      robotComments = commentsUtil.robotCommentsByChange(notes());
+    }
+    return robotComments;
+  }
+
+  public Integer unresolvedCommentCount() throws OrmException {
+    if (unresolvedCommentCount == null) {
+      if (!lazyLoad) {
+        return null;
+      }
+      Long count =
+          Stream.concat(publishedComments().stream(), robotComments().stream())
+              .filter(c -> (c.unresolved == Boolean.TRUE))
+              .count();
+      unresolvedCommentCount = count.intValue();
+    }
+    return unresolvedCommentCount;
+  }
+
+  public void setUnresolvedCommentCount(Integer count) {
+    this.unresolvedCommentCount = count;
+  }
+
   public List<ChangeMessage> messages() throws OrmException {
     if (messages == null) {
       if (!lazyLoad) {
@@ -1180,23 +1214,6 @@
     this.hashtags = hashtags;
   }
 
-  @Deprecated
-  public Set<Account.Id> starredBy() throws OrmException {
-    if (starredByUser == null) {
-      if (!lazyLoad) {
-        return Collections.emptySet();
-      }
-      starredByUser =
-          checkNotNull(starredChangesUtil).byChange(legacyId, StarredChangesUtil.DEFAULT_LABEL);
-    }
-    return starredByUser;
-  }
-
-  @Deprecated
-  public void setStarredBy(Set<Account.Id> starredByUser) {
-    this.starredByUser = starredByUser;
-  }
-
   public ImmutableListMultimap<Account.Id, String> stars() throws OrmException {
     if (stars == null) {
       if (!lazyLoad) {
@@ -1225,15 +1242,23 @@
     return starRefs;
   }
 
-  @AutoValue
-  abstract static class ReviewedByEvent {
-    private static ReviewedByEvent create(ChangeMessage msg) {
-      return new AutoValue_ChangeData_ReviewedByEvent(msg.getAuthor(), msg.getWrittenOn());
+  public Set<String> stars(Account.Id accountId) throws OrmException {
+    if (starsOf != null) {
+      if (!starsOf.accountId().equals(accountId)) {
+        starsOf = null;
+      }
     }
-
-    public abstract Account.Id author();
-
-    public abstract Timestamp ts();
+    if (starsOf == null) {
+      if (stars != null) {
+        starsOf = StarsOf.create(accountId, stars.get(accountId));
+      } else {
+        if (!lazyLoad) {
+          return ImmutableSet.of();
+        }
+        starsOf = StarsOf.create(accountId, starredChangesUtil.getLabels(accountId, legacyId));
+      }
+    }
+    return starsOf.stars();
   }
 
   @Override
@@ -1272,4 +1297,26 @@
   public void setRefStatePatterns(Iterable<byte[]> refStatePatterns) {
     this.refStatePatterns = ImmutableList.copyOf(refStatePatterns);
   }
+
+  @AutoValue
+  abstract static class ReviewedByEvent {
+    private static ReviewedByEvent create(ChangeMessage msg) {
+      return new AutoValue_ChangeData_ReviewedByEvent(msg.getAuthor(), msg.getWrittenOn());
+    }
+
+    public abstract Account.Id author();
+
+    public abstract Timestamp ts();
+  }
+
+  @AutoValue
+  abstract static class StarsOf {
+    private static StarsOf create(Account.Id accountId, Iterable<String> stars) {
+      return new AutoValue_ChangeData_StarsOf(accountId, ImmutableSortedSet.copyOf(stars));
+    }
+
+    public abstract Account.Id accountId();
+
+    public abstract ImmutableSortedSet<String> stars();
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
index 2af5cd8..aa220e1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
@@ -159,6 +159,7 @@
   public static final String FIELD_STATUS = "status";
   public static final String FIELD_SUBMISSIONID = "submissionid";
   public static final String FIELD_TR = "tr";
+  public static final String FIELD_UNRESOLVED_COMMENT_COUNT = "unresolved";
   public static final String FIELD_VISIBLETO = "visibleto";
   public static final String FIELD_WATCHEDBY = "watchedby";
 
@@ -513,6 +514,10 @@
       return new EditByPredicate(self());
     }
 
+    if ("unresolved".equalsIgnoreCase(value)) {
+      return new IsUnresolvedPredicate();
+    }
+
     // for plugins the value will be operandName_pluginName
     String[] names = value.split("_");
     if (names.length == 2) {
@@ -677,7 +682,7 @@
     // label:CodeReview=1,jsmith or
     // label:CodeReview=1,group=android_approvers or
     // label:CodeReview=1,android_approvers
-    //  user/groups without a label will first attempt to match user
+    // user/groups without a label will first attempt to match user
     // Special case: votes by owners can be tracked with ",owner":
     // label:Code-Review+2,owner
     // label:Code-Review+2,user=owner
@@ -1056,6 +1061,11 @@
     return new SubmittablePredicate(status);
   }
 
+  @Operator
+  public Predicate<ChangeData> unresolved(String value) throws QueryParseException {
+    return new IsUnresolvedPredicate(value);
+  }
+
   @Override
   protected Predicate<ChangeData> defaultField(String query) throws QueryParseException {
     if (query.startsWith("refs/")) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsUnresolvedPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsUnresolvedPredicate.java
new file mode 100644
index 0000000..17a6347
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsUnresolvedPredicate.java
@@ -0,0 +1,34 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.query.change;
+
+import com.google.gerrit.server.index.change.ChangeField;
+import com.google.gerrit.server.query.QueryParseException;
+import com.google.gwtorm.server.OrmException;
+
+public class IsUnresolvedPredicate extends IntegerRangeChangePredicate {
+  IsUnresolvedPredicate() throws QueryParseException {
+    this(">0");
+  }
+
+  IsUnresolvedPredicate(String value) throws QueryParseException {
+    super(ChangeField.UNRESOLVED_COMMENT_COUNT, value);
+  }
+
+  @Override
+  protected Integer getValueInt(ChangeData changeData) throws OrmException {
+    return ChangeField.UNRESOLVED_COMMENT_COUNT.get(changeData, null);
+  }
+}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/HtmlParserTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/HtmlParserTest.java
index 7d729bc..11bc4ff 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/HtmlParserTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/HtmlParserTest.java
@@ -41,7 +41,7 @@
     b.htmlContent(
         newHtmlBody(
             "Looks good to me",
-            "I have a comment on this.",
+            "I have a comment on this.&nbsp;",
             null,
             "Also have a comment here.",
             null,
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/ParserUtilTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/ParserUtilTest.java
new file mode 100644
index 0000000..dfa492c
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/ParserUtilTest.java
@@ -0,0 +1,58 @@
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.mail.receive;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+
+public class ParserUtilTest {
+  @Test
+  public void trimQuotationLineOnMessageWithoutQuoatationLine() throws Exception {
+    assertThat(ParserUtil.trimQuotation("One line")).isEqualTo("One line");
+    assertThat(ParserUtil.trimQuotation("Two\nlines")).isEqualTo("Two\nlines");
+    assertThat(ParserUtil.trimQuotation("Thr\nee\nlines")).isEqualTo("Thr\nee\nlines");
+  }
+
+  @Test
+  public void trimQuotationLineOnMixedMessages() throws Exception {
+    assertThat(
+            ParserUtil.trimQuotation(
+                "One line\n"
+                    + "On Thu, Feb 9, 2017 at 8:21 AM, ekempin (Gerrit)\n"
+                    + "<noreply-gerritcodereview-qUgXfQecoDLHwp0MldAzig@google.com> wrote:"))
+        .isEqualTo("One line");
+    assertThat(
+            ParserUtil.trimQuotation(
+                "One line\n"
+                    + "On Thu, Feb 9, 2017 at 8:21 AM, ekempin (Gerrit) "
+                    + "<noreply-gerritcodereview-qUgXfQecoDLHwp0MldAzig@google.com> wrote:"))
+        .isEqualTo("One line");
+  }
+
+  @Test
+  public void trimQuotationLineOnMessagesContainingQuoationLine() throws Exception {
+    assertThat(
+            ParserUtil.trimQuotation(
+                "On Thu, Feb 9, 2017 at 8:21 AM, ekempin (Gerrit)\n"
+                    + "<noreply-gerritcodereview-qUgXfQecoDLHwp0MldAzig@google.com> wrote:"))
+        .isEqualTo("");
+    assertThat(
+            ParserUtil.trimQuotation(
+                "On Thu, Feb 9, 2017 at 8:21 AM, ekempin (Gerrit) "
+                    + "<noreply-gerritcodereview-qUgXfQecoDLHwp0MldAzig@google.com> wrote:"))
+        .isEqualTo("");
+  }
+}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/TextParserTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/TextParserTest.java
index b23b341..a98835b 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/TextParserTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/TextParserTest.java
@@ -180,6 +180,8 @@
   private static String newPlaintextBody(
       String changeMessage, String c1, String c2, String c3, String f1, String f2, String fc1) {
     return (changeMessage == null ? "" : changeMessage + "\n")
+        + "On Thu, Feb 9, 2017 at 8:21 AM, ekempin (Gerrit)\n"
+        + "<noreply-gerritcodereview-qUgXfQecoDLHwp0MldAzig@google.com> wrote: \n"
         + "> Foo Bar has posted comments on this change. (  \n"
         + "> "
         + changeURL
@@ -217,6 +219,7 @@
         + "> Should entry.getKey() be included in this message?\n"
         + "> \n"
         + (c1 == null ? "" : c1 + "\n")
+        + ">\n"
         + "> \n"
         + "> "
         + changeURL
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
index bf3e618..1b9fc61 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
@@ -1549,6 +1549,37 @@
   }
 
   @Test
+  public void byUnresolved() throws Exception {
+    TestRepository<Repo> repo = createProject("repo");
+    Change change1 = insert(repo, newChange(repo));
+    Change change2 = insert(repo, newChange(repo));
+    Change change3 = insert(repo, newChange(repo));
+
+    // Change1 has one resolved comment (unresolvedcount = 0)
+    // Change2 has one unresolved comment (unresolvedcount = 1)
+    // Change3 has one resolved comment and one unresolved comment (unresolvedcount = 1)
+    addComment(change1.getChangeId(), "comment 1", false);
+    addComment(change2.getChangeId(), "comment 2", true);
+    addComment(change3.getChangeId(), "comment 3", false);
+    addComment(change3.getChangeId(), "comment 4", true);
+
+    assertQuery("has:unresolved", change3, change2);
+
+    assertQuery("unresolved:0", change1);
+    List<ChangeInfo> changeInfos = assertQuery("unresolved:>=0", change3, change2, change1);
+    assertThat(changeInfos.get(0).unresolvedCommentCount).isEqualTo(1); // Change3
+    assertThat(changeInfos.get(1).unresolvedCommentCount).isEqualTo(1); // Change2
+    assertThat(changeInfos.get(2).unresolvedCommentCount).isEqualTo(0); // Change1
+    assertQuery("unresolved:>0", change3, change2);
+
+    assertQuery("unresolved:<1", change1);
+    assertQuery("unresolved:<=1", change3, change2, change1);
+    assertQuery("unresolved:1", change3, change2);
+    assertQuery("unresolved:>1");
+    assertQuery("unresolved:>=1", change3, change2);
+  }
+
+  @Test
   public void byCommitsOnBranchNotMerged() throws Exception {
     TestRepository<Repo> repo = createProject("repo");
     int n = 10;
@@ -1595,6 +1626,7 @@
     cd.changedLines();
     cd.reviewedBy();
     cd.reviewers();
+    cd.unresolvedCommentCount();
 
     // TODO(dborowitz): Swap out GitRepositoryManager somehow? Will probably be
     // necessary for NoteDb anyway.
@@ -1932,4 +1964,16 @@
   protected static long lastUpdatedMs(Change c) {
     return c.getLastUpdatedOn().getTime();
   }
+
+  private void addComment(int changeId, String message, Boolean unresolved) throws Exception {
+    ReviewInput input = new ReviewInput();
+    ReviewInput.CommentInput comment = new ReviewInput.CommentInput();
+    comment.line = 1;
+    comment.message = message;
+    comment.unresolved = unresolved;
+    input.comments =
+        ImmutableMap.<String, List<ReviewInput.CommentInput>>of(
+            Patch.COMMIT_MSG, ImmutableList.<ReviewInput.CommentInput>of(comment));
+    gApi.changes().id(changeId).current().review(input);
+  }
 }
diff --git a/lib/BUILD b/lib/BUILD
index ca0fec3..fe1933c 100644
--- a/lib/BUILD
+++ b/lib/BUILD
@@ -231,6 +231,17 @@
 )
 
 java_library(
+    name = "truth-java8-extension",
+    data = ["//lib:LICENSE-DO_NOT_DISTRIBUTE"],
+    visibility = ["//visibility:public"],
+    exports = [
+        ":guava",
+        ":truth",
+        "@truth-java8-extension//jar",
+    ],
+)
+
+java_library(
     name = "javassist",
     data = ["//lib:LICENSE-DO_NOT_DISTRIBUTE"],
     visibility = ["//visibility:public"],
diff --git a/lib/js/bower_archives.bzl b/lib/js/bower_archives.bzl
index aaa8b81..67cc7c0 100644
--- a/lib/js/bower_archives.bzl
+++ b/lib/js/bower_archives.bzl
@@ -75,8 +75,8 @@
   bower_archive(
     name = "mocha",
     package = "mocha",
-    version = "2.5.3",
-    sha1 = "22ef0d1f43ba5e2241369c501ac648f00c0440c0")
+    version = "3.2.0",
+    sha1 = "b77f23f7ad1f1363501bcae96f0f4f47745dad0f")
   bower_archive(
     name = "neon-animation",
     package = "neon-animation",
@@ -105,5 +105,5 @@
   bower_archive(
     name = "webcomponentsjs",
     package = "webcomponentsjs",
-    version = "0.7.22",
-    sha1 = "8ba97a4a279ec6973a19b171c462a7b5cf454fb9")
+    version = "0.7.23",
+    sha1 = "3d62269e614175573b0a0f3039aab05d40f0a763")
diff --git a/polygerrit-ui/app/.gitignore b/polygerrit-ui/app/.gitignore
new file mode 100644
index 0000000..375a75d
--- /dev/null
+++ b/polygerrit-ui/app/.gitignore
@@ -0,0 +1 @@
+/plugins/
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.html b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.html
index b094e1c..7b0eeb9 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.html
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.html
@@ -69,11 +69,11 @@
     });
 
     suite('test show change number preference enabled', function() {
-      return setup(function() {
-         return stubRestAPI({legacycid_in_change_table: true,
-            time_format: 'HHMM_12',
-            change_table: [],
-          }).then(function() {
+      setup(function() {
+        return stubRestAPI({legacycid_in_change_table: true,
+           time_format: 'HHMM_12',
+           change_table: [],
+        }).then(function() {
           element = fixture('basic');
           return element._loadPreferences();
         });
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
index 0a7512e..7c8d92e 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
@@ -1026,7 +1026,7 @@
 
     _computeCommitClass: function(collapsed, commitMessage) {
       if (this._computeCommitToggleHidden(commitMessage)) { return ''; }
-      return collapsed ? 'commitCollapsed' : '';
+      return collapsed ? 'collapsed' : '';
     },
 
     _computeCollapseCommitText: function(collapsed) {
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
index 75201e4..e36a55b 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
@@ -964,11 +964,11 @@
         element._latestCommitMessage = _.times(31, String).join('\n');
         assert.isTrue(element._commitCollapsed);
         assert.isTrue(
-            element.$.commitMessage.classList.contains('commitCollapsed'));
+            element.$.commitMessage.classList.contains('collapsed'));
         MockInteractions.tap(element.$.commitCollapseToggleButton);
         assert.isFalse(element._commitCollapsed);
         assert.isFalse(
-            element.$.commitMessage.classList.contains('commitCollapsed'));
+            element.$.commitMessage.classList.contains('collapsed'));
       });
     });
   });
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.js b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.js
index c27c57e..d62dbf8 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.js
@@ -340,7 +340,7 @@
     var threadGroupEl =
         document.createElement('gr-diff-comment-thread-group');
     threadGroupEl.changeNum = changeNum;
-    threadGroupEl.patchNum = patchNum;
+    threadGroupEl.patchForNewThreads = patchNum;
     threadGroupEl.path = path;
     threadGroupEl.side = side;
     threadGroupEl.projectConfig = projectConfig;
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder_test.html b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder_test.html
index 4665bdf..e5fe1d7 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder_test.html
@@ -241,7 +241,7 @@
 
       function checkThreadGroupProps(threadGroupEl, patchNum, side, comments) {
         assert.equal(threadGroupEl.changeNum, '42');
-        assert.equal(threadGroupEl.patchNum, patchNum);
+        assert.equal(threadGroupEl.patchForNewThreads, patchNum);
         assert.equal(threadGroupEl.path, '/path/to/foo');
         assert.equal(threadGroupEl.side, side);
         assert.deepEqual(threadGroupEl.projectConfig, {foo: 'bar'});
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment-thread-group/gr-diff-comment-thread-group.html b/polygerrit-ui/app/elements/diff/gr-diff-comment-thread-group/gr-diff-comment-thread-group.html
index 2c44813..95de61f 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-comment-thread-group/gr-diff-comment-thread-group.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-comment-thread-group/gr-diff-comment-thread-group.html
@@ -24,15 +24,18 @@
         display: block;
         white-space: normal;
       }
+      gr-diff-comment-thread + gr-diff-comment-thread {
+        margin-top: .2em;
+      }
     </style>
-    <template is="dom-repeat" items="[[_threadGroups]]"
+    <template is="dom-repeat" items="[[_threads]]"
         as="thread">
       <gr-diff-comment-thread
           comments="[[thread.comments]]"
           comment-side="[[thread.commentSide]]"
           change-num="[[changeNum]]"
           location-range="[[thread.locationRange]]"
-          patch-num="[[patchNum]]"
+          patch-num="[[thread.patchNum]]"
           path="[[path]]"
           side="[[side]]"
           project-config="[[projectConfig]]"></gr-diff-comment-thread>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment-thread-group/gr-diff-comment-thread-group.js b/polygerrit-ui/app/elements/diff/gr-diff-comment-thread-group/gr-diff-comment-thread-group.js
index bcd9b27..ee4c5b3 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-comment-thread-group/gr-diff-comment-thread-group.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-comment-thread-group/gr-diff-comment-thread-group.js
@@ -23,14 +23,14 @@
         type: Array,
         value: function() { return []; },
       },
-      patchNum: String,
+      patchForNewThreads: String,
       projectConfig: Object,
       range: Object,
       side: {
         type: String,
         value: 'REVISION',
       },
-      _threadGroups: {
+      _threads: {
         type: Array,
         value: function() { return []; },
       },
@@ -40,17 +40,18 @@
       '_commentsChanged(comments.*)',
     ],
 
-    addNewThread: function(locationRange, commentSide) {
-      this.push('_threadGroups', {
+    addNewThread: function(locationRange) {
+      this.push('_threads', {
         comments: [],
         locationRange: locationRange,
+        patchNum: this.patchForNewThreads,
       });
     },
 
     removeThread: function(locationRange) {
-      for (var i = 0; i < this._threadGroups.length; i++) {
-        if (this._threadGroups[i].locationRange === locationRange) {
-          this.splice('_threadGroups', i, 1);
+      for (var i = 0; i < this._threads.length; i++) {
+        if (this._threads[i].locationRange === locationRange) {
+          this.splice('_threads', i, 1);
           return;
         }
       }
@@ -68,12 +69,20 @@
     },
 
     _commentsChanged: function() {
-      this._threadGroups = this._getThreadGroups(this.comments);
+      this._threads = this._getThreadGroups(this.comments);
     },
 
     _sortByDate: function(threadGroups) {
       if (!threadGroups.length) { return; }
       return threadGroups.sort(function(a, b) {
+        // If a comment is a draft, it doesn't have a start_datetime yet.
+        // Assume it is newer than the comment it is being compared to.
+        if (!a.start_datetime) {
+          return 1;
+        }
+        if (!b.start_datetime) {
+          return -1;
+        }
         return util.parseDate(a.start_datetime) -
             util.parseDate(b.start_datetime);
       });
@@ -87,6 +96,16 @@
           comment.__commentSide;
     },
 
+    /**
+     * Determines what the patchNum of a thread should be. Use patchNum from
+     * comment if it exists, otherwise the property of the thread group.
+     * This is needed for switching between side-by-side and unified views when
+     * there are unsaved drafts.
+     */
+    _getPatchNum: function(comment) {
+      return comment.patchNum || this.patchForNewThreads;
+    },
+
     _getThreadGroups: function(comments) {
       var threadGroups = {};
 
@@ -106,6 +125,7 @@
             comments: [comment],
             locationRange: locationRange,
             commentSide: comment.__commentSide,
+            patchNum: this._getPatchNum(comment),
           };
         }
       }.bind(this));
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment-thread-group/gr-diff-comment-thread-group_test.html b/polygerrit-ui/app/elements/diff/gr-diff-comment-thread-group/gr-diff-comment-thread-group_test.html
index 7ae9b9d..bc08bab 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-comment-thread-group/gr-diff-comment-thread-group_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-comment-thread-group/gr-diff-comment-thread-group_test.html
@@ -49,6 +49,7 @@
     });
 
     test('_getThreadGroups', function() {
+      element.patchForNewThreads = 3;
       var comments = [
         {
           id: 'sallys_confession',
@@ -79,12 +80,14 @@
               __commentSide: 'left',
             }],
           locationRange: 'line-left',
+          patchNum: 3
         },
       ];
 
       assert.deepEqual(element._getThreadGroups(comments),
           expectedThreadGroups);
 
+      // Patch num should get inherited from comment rather
       comments.push({
           id: 'betsys_confession',
           message: 'i like you, jack',
@@ -113,6 +116,7 @@
               updated: '2015-12-24 15:00:20.396000000',
               __commentSide: 'left',
             }],
+          patchNum: 3,
           locationRange: 'line-left',
         },
         {
@@ -130,6 +134,7 @@
             },
             __commentSide: 'left',
           }],
+          patchNum: 3,
           locationRange: 'range-1-1-1-2-left',
         },
       ];
@@ -165,6 +170,32 @@
       ];
 
       assert.deepEqual(element._sortByDate(threadGroups), expectedResult);
+
+      // When a comment doesn't have a date, the one without the date should be
+      // last.
+      var threadGroups = [
+        {
+          start_datetime: '2015-12-23 15:00:20.396000000',
+          comments: [],
+          locationRange: 'line',
+        },
+        {
+          comments: [],
+          locationRange: 'range-1-1-1-2',
+        },
+      ];
+
+      var expectedResult = [
+        {
+          start_datetime: '2015-12-23 15:00:20.396000000',
+          comments: [],
+          locationRange: 'line',
+        },
+        {
+          comments: [],
+          locationRange: 'range-1-1-1-2',
+        },
+      ];
     });
 
     test('_calculateLocationRange', function() {
@@ -192,21 +223,33 @@
 
     test('addNewThread', function() {
       var locationRange = 'range-1-2-3-4';
-      element._threadGroups = [{locationRange: 'line'}];
+      element._threads = [{locationRange: 'line'}];
       element.addNewThread(locationRange);
-      assert(element._threadGroups.length, 2);
+      assert(element._threads.length, 2);
+    });
+
+    test('_getPatchNum', function() {
+      element.patchForNewThreads = 3;
+      var comment = {
+        id: 'sallys_confession',
+        message: 'i like you, jack',
+        updated: '2015-12-23 15:00:20.396000000',
+      };
+      assert.equal(element._getPatchNum(comment), 3);
+      comment.patchNum = 4;
+      assert.equal(element._getPatchNum(comment), 4);
     });
 
     test('removeThread', function() {
       var locationRange = 'range-1-2-3-4';
-      element._threadGroups = [
+      element._threads = [
         {locationRange: 'range-1-2-3-4', comments: []},
         {locationRange: 'line', comments: []}
       ];
       flushAsynchronousOperations();
       element.removeThread(locationRange);
       flushAsynchronousOperations();
-      assert(element._threadGroups.length, 1);
+      assert(element._threads.length, 1);
     });
   });
 </script>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread.html b/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread.html
index fcd7574..7a60317 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread.html
@@ -28,6 +28,10 @@
         margin-bottom: 1px;
         white-space: normal;
       }
+      .actions {
+        border-top: 1px dotted #bbb;
+        padding: .5em .7em;
+      }
       #container {
         background-color: #fcfad6;
       }
@@ -36,21 +40,31 @@
       }
     </style>
     <div id="container" class$="[[_computeHostClass(_unresolved)]]">
-      <template id="commentList" is="dom-repeat" items="[[_orderedComments]]" as="comment">
+      <template id="commentList" is="dom-repeat" items="[[_orderedComments]]"
+          as="comment">
         <gr-diff-comment
             comment="{{comment}}"
+            robot-button-disabled="[[_hideActions(_showActions, _lastComment)]]"
             change-num="[[changeNum]]"
             patch-num="[[patchNum]]"
             draft="[[comment.__draft]]"
             show-actions="[[_showActions]]"
             comment-side="[[comment.__commentSide]]"
             project-config="[[projectConfig]]"
-            on-comment-discard="_handleCommentDiscard"
-            on-create-ack-comment="_handleCommentAck"
-            on-create-done-comment="_handleCommentDone"
             on-create-fix-comment="_handleCommentFix"
-            on-create-reply-comment="_handleCommentReply"></gr-diff-comment>
+            on-comment-discard="_handleCommentDiscard"></gr-diff-comment>
       </template>
+      <div class="actions"
+          hidden$="[[_hideActions(_showActions, _lastComment)]]">
+        <gr-button id="replyBtn" class="action reply"
+            on-tap="_handleCommentReply">Reply</gr-button>
+        <gr-button id="quoteBtn" class="action quote"
+            on-tap="_handleCommentQuote">Quote</gr-button>
+        <gr-button id="ackBtn" class="action ack" on-tap="_handleCommentAck">
+          Ack</gr-button>
+        <gr-button id="doneBtn" class="action done" on-tap="_handleCommentDone">
+          Done</gr-button>
+      </div>
     </div>
     <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
     <gr-storage id="storage"></gr-storage>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread.js b/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread.js
index f3ee6ac..c088b1a 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread.js
@@ -47,6 +47,7 @@
       },
 
       _showActions: Boolean,
+      _lastComment: Object,
       _orderedComments: Array,
       _unresolved: {
         type: Boolean,
@@ -77,16 +78,21 @@
       this._setInitialExpandedState();
     },
 
-    addOrEditDraft: function(opt_lineNum) {
-      var lastComment = this.comments[this.comments.length - 1];
-      if (lastComment && lastComment.__draft) {
+    addOrEditDraft: function(opt_lineNum, opt_range) {
+      var lastComment = this.comments[this.comments.length - 1] || {};
+      if (lastComment.__draft) {
         var commentEl = this._commentElWithDraftID(
             lastComment.id || lastComment.__draftID);
         commentEl.editing = true;
+
+        // If the comment was collapsed, re-open it to make it clear which
+        // actions are available.
+        commentEl.collapsed = false;
       } else {
-        this.addDraft(opt_lineNum,
-            lastComment ? lastComment.range : undefined,
-            lastComment ? lastComment.unresolved : undefined);
+        var range = opt_range ? opt_range :
+            lastComment ? lastComment.range : undefined;
+        var unresolved = lastComment ? lastComment.unresolved : undefined;
+        this.addDraft(opt_lineNum, range, unresolved);
       }
     },
 
@@ -103,7 +109,14 @@
 
     _commentsChanged: function(changeRecord) {
       this._orderedComments = this._sortedComments(this.comments);
-      this._unresolved = this._getLastComment().unresolved;
+      if (this._orderedComments.length) {
+        this._lastComment = this._getLastComment();
+        this._unresolved = this._lastComment.unresolved;
+      }
+    },
+
+    _hideActions: function(_showActions, _lastComment) {
+      return !_showActions || !_lastComment || !!_lastComment.__draft;
     },
 
     _getLastComment: function() {
@@ -192,23 +205,31 @@
       }
     },
 
-    _handleCommentReply: function(e) {
-      var comment = e.detail.comment;
+    _processCommentReply: function(opt_quote) {
+      var comment = this._lastComment;
       var quoteStr;
-      if (e.detail.quote) {
+      if (opt_quote) {
         var msg = comment.message;
         quoteStr = '> ' + msg.replace(NEWLINE_PATTERN, '\n> ') + '\n\n';
       }
       this._createReplyComment(comment, quoteStr, true, comment.unresolved);
     },
 
+    _handleCommentReply: function(e) {
+      this._processCommentReply();
+    },
+
+    _handleCommentQuote: function(e) {
+      this._processCommentReply(true);
+    },
+
     _handleCommentAck: function(e) {
-      var comment = e.detail.comment;
+      var comment = this._lastComment;
       this._createReplyComment(comment, 'Ack', false, comment.unresolved);
     },
 
     _handleCommentDone: function(e) {
-      var comment = e.detail.comment;
+      var comment = this._lastComment;
       this._createReplyComment(comment, 'Done', false, false);
     },
 
@@ -250,6 +271,7 @@
         __draftID: Math.random().toString(36),
         __date: new Date(),
         path: this.path,
+        patchNum: this.patchNum,
         side: this.side,
         __commentSide: this.commentSide,
       };
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread_test.html b/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread_test.html
index 30c6fed..e0ae9ff 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread_test.html
@@ -43,6 +43,7 @@
     var sandbox;
 
     setup(function() {
+      sandbox = sinon.sandbox.create();
       stub('gr-rest-api-interface', {
         getLoggedIn: function() { return Promise.resolve(false); },
       });
@@ -147,6 +148,17 @@
       assert.isFalse(commentElStub.called);
       assert.isTrue(addDraftStub.called);
     });
+
+    test('_hideActions', function() {
+      var showActions = true;
+      var lastComment = {};
+      assert.equal(element._hideActions(showActions, lastComment), false);
+      showActions = false;
+      assert.equal(element._hideActions(showActions, lastComment), true);
+      var showActions = true;
+      lastComment.__draft = true;
+      assert.equal(element._hideActions(showActions, lastComment), true);
+    });
   });
 
   suite('comment action tests', function() {
@@ -189,33 +201,35 @@
     test('reply', function(done) {
       var commentEl = element.$$('gr-diff-comment');
       assert.ok(commentEl);
-      commentEl.addEventListener('create-reply-comment', function() {
-        var drafts = element._orderedComments.filter(function(c) {
-          return c.__draft == true;
-        });
-        assert.equal(drafts.length, 1);
-        assert.notOk(drafts[0].message, 'message should be empty');
-        assert.equal(drafts[0].in_reply_to, 'baf0414d_60047215');
-        done();
+
+      var replyBtn = element.$.replyBtn;
+      MockInteractions.tap(replyBtn);
+      flushAsynchronousOperations();
+
+      var drafts = element._orderedComments.filter(function(c) {
+        return c.__draft == true;
       });
-      commentEl.fire('create-reply-comment', {comment: commentEl.comment},
-          {bubbles: false});
+      assert.equal(drafts.length, 1);
+      assert.notOk(drafts[0].message, 'message should be empty');
+      assert.equal(drafts[0].in_reply_to, 'baf0414d_60047215');
+      done();
     });
 
     test('quote reply', function(done) {
       var commentEl = element.$$('gr-diff-comment');
       assert.ok(commentEl);
-      commentEl.addEventListener('create-reply-comment', function() {
-        var drafts = element._orderedComments.filter(function(c) {
+
+      var quoteBtn = element.$.quoteBtn;
+      MockInteractions.tap(quoteBtn);
+      flushAsynchronousOperations();
+
+      var drafts = element._orderedComments.filter(function(c) {
           return c.__draft == true;
-        });
-        assert.equal(drafts.length, 1);
-        assert.equal(drafts[0].message, '> is this a crossover episode!?\n\n');
-        assert.equal(drafts[0].in_reply_to, 'baf0414d_60047215');
-        done();
       });
-      commentEl.fire('create-reply-comment', {comment: commentEl.comment,
-          quote: true}, {bubbles: false});
+      assert.equal(drafts.length, 1);
+      assert.equal(drafts[0].message, '> is this a crossover episode!?\n\n');
+      assert.equal(drafts[0].in_reply_to, 'baf0414d_60047215');
+      done();
     });
 
     test('quote reply multiline', function(done) {
@@ -233,27 +247,32 @@
 
       var commentEl = element.$$('gr-diff-comment');
       assert.ok(commentEl);
-      commentEl.addEventListener('create-reply-comment', function() {
-        var drafts = element._orderedComments.filter(function(c) {
-          return c.__draft == true;
-        });
-        assert.equal(drafts.length, 1);
-        assert.equal(drafts[0].message,
-            '> is this a crossover episode!?\n> It might be!\n\n');
-        assert.equal(drafts[0].in_reply_to, 'baf0414d_60047215');
-        done();
+
+      var quoteBtn = element.$.quoteBtn;
+      MockInteractions.tap(quoteBtn);
+      flushAsynchronousOperations();
+
+      var drafts = element._orderedComments.filter(function(c) {
+        return c.__draft == true;
       });
-      commentEl.fire('create-reply-comment', {comment: commentEl.comment,
-          quote: true}, {bubbles: false});
+      assert.equal(drafts.length, 1);
+      assert.equal(drafts[0].message,
+          '> is this a crossover episode!?\n> It might be!\n\n');
+      assert.equal(drafts[0].in_reply_to, 'baf0414d_60047215');
+      done();
     });
 
     test('ack', function(done) {
       element.changeNum = '42';
       element.patchNum = '1';
+
       var commentEl = element.$$('gr-diff-comment');
       assert.ok(commentEl);
-      commentEl.addEventListener('create-ack-comment', function() {
-        var drafts = element._orderedComments.filter(function(c) {
+
+      var ackBtn = element.$.ackBtn;
+      MockInteractions.tap(ackBtn);
+      flush(function() {
+        var drafts = element.comments.filter(function(c) {
           return c.__draft == true;
         });
         assert.equal(drafts.length, 1);
@@ -261,8 +280,6 @@
         assert.equal(drafts[0].in_reply_to, 'baf0414d_60047215');
         done();
       });
-      commentEl.fire('create-ack-comment', {comment: commentEl.comment},
-          {bubbles: false});
     });
 
     test('done', function(done) {
@@ -270,8 +287,11 @@
       element.patchNum = '1';
       var commentEl = element.$$('gr-diff-comment');
       assert.ok(commentEl);
-      commentEl.addEventListener('create-done-comment', function() {
-        var drafts = element._orderedComments.filter(function(c) {
+
+      var doneBtn = element.$.doneBtn;
+      MockInteractions.tap(doneBtn);
+      flush(function() {
+        var drafts = element.comments.filter(function(c) {
           return c.__draft == true;
         });
         assert.equal(drafts.length, 1);
@@ -280,8 +300,6 @@
         assert.isFalse(drafts[0].unresolved);
         done();
       });
-      commentEl.fire('create-done-comment', {comment: commentEl.comment},
-          {bubbles: false});
     });
 
     test('please fix', function(done) {
@@ -328,7 +346,7 @@
     });
 
     test('first editing comment does not add __otherEditing attribute',
-        function(done) {
+        function() {
       var commentEl = element.$$('gr-diff-comment');
       element.comments = [{
         author: {
@@ -341,101 +359,16 @@
         updated: '2015-12-08 19:48:33.843000000',
         __draft: true,
       }];
+
+      var replyBtn = element.$.replyBtn;
+      MockInteractions.tap(replyBtn);
       flushAsynchronousOperations();
 
-      commentEl.addEventListener('create-reply-comment', function() {
-        var editing = element._orderedComments.filter(function(c) {
-          return c.__editing == true;
-        });
-        assert.equal(editing.length, 1);
-        assert.equal(!!editing[0].__otherEditing, false);
-        done();
+      var editing = element._orderedComments.filter(function(c) {
+        return c.__editing == true;
       });
-      commentEl.fire('create-reply-comment', {comment: commentEl.comment},
-          {bubbles: false});
-    });
-
-    test('two editing comments adds __otherEditing attribute', function(done) {
-      var commentEl = element.$$('gr-diff-comment');
-      element.comments = [{
-        author: {
-          name: 'Mr. Peanutbutter',
-          email: 'tenn1sballchaser@aol.com',
-        },
-        id: 'baf0414d_60047215',
-        line: 5,
-        message: 'is this a crossover episode!?',
-        updated: '2015-12-08 19:48:33.843000000',
-        __editing: true,
-        __draft: true,
-      }];
-      flushAsynchronousOperations();
-
-      commentEl.addEventListener('create-reply-comment', function() {
-        var editing = element._orderedComments.filter(function(c) {
-          return c.__editing == true;
-        });
-        assert.equal(editing.length, 2);
-        assert.equal(editing[1].__otherEditing, true);
-        done();
-      });
-      commentEl.fire('create-reply-comment', {comment: commentEl.comment},
-          {bubbles: false});
-    });
-
-    test('When editing other comments, local storage set after discard',
-        function(done) {
-      element.changeNum = '42';
-      element.patchNum = '1';
-      element.comments = [{
-        author: {
-          name: 'Mr. Peanutbutter',
-          email: 'tenn1sballchaser@aol.com',
-        },
-        id: 'baf0414d_60047215',
-        in_reply_to: 'baf0414d_60047215',
-        line: 5,
-        message: 'is this a crossover episode!?',
-        updated: '2015-12-08 19:48:31.843000000',
-      },
-      {
-        author: {
-          name: 'Mr. Peanutbutter',
-          email: 'tenn1sballchaser@aol.com',
-        },
-        __draftID: '1',
-        in_reply_to: 'baf0414d_60047215',
-        line: 5,
-        message: 'yes',
-        updated: '2015-12-08 19:48:32.843000000',
-        __draft: true,
-        __editing: true,
-      },
-      {
-        author: {
-          name: 'Mr. Peanutbutter',
-          email: 'tenn1sballchaser@aol.com',
-        },
-        __draftID: '2',
-        in_reply_to: 'baf0414d_60047215',
-        line: 5,
-        message: 'no',
-        updated: '2015-12-08 19:48:33.843000000',
-        __draft: true,
-        __editing: true,
-      }];
-      var storageStub = sinon.stub(element.$.storage, 'setDraftComment');
-      flushAsynchronousOperations();
-
-      var draftEl =
-          Polymer.dom(element.root).querySelectorAll('gr-diff-comment')[1];
-      assert.ok(draftEl);
-      draftEl.addEventListener('comment-discard', function() {
-        assert.isTrue(storageStub.called);
-        storageStub.restore();
-        done();
-      });
-      draftEl.fire('comment-discard', null, {bubbles: false});
+      assert.equal(editing.length, 1);
+      assert.equal(!!editing[0].__otherEditing, false);
     });
 
     test('When not editing other comments, local storage not set after discard',
@@ -599,8 +532,21 @@
 
     test('_newDraft', function() {
       element.commentSide = 'left';
+      element.patchNum = 3;
       var draft = element._newDraft();
       assert.equal(draft.__commentSide, 'left');
+      assert.equal(draft.patchNum, 3);
+    });
+
+    test('new comment gets created', function() {
+      element.comments = [];
+      element.addOrEditDraft(1);
+      assert.equal(element.comments.length, 1);
+      // Mock a submitted comment.
+      element.comments[0].id = element.comments[0].__draftID;
+      element.comments[0].__draft = false;
+      element.addOrEditDraft(1);
+      assert.equal(element.comments.length, 2);
     });
   });
 </script>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.html b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.html
index ceedf73..8863aa6 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.html
@@ -134,7 +134,7 @@
         margin-top: -.4em;
       }
       .runIdInformation {
-        margin-bottom: .5em;
+        margin: 1em 0;
       }
       .robotRun {
         margin-left: .5em;
@@ -218,6 +218,7 @@
           on-keydown="_handleTextareaKeydown"></iron-autogrow-textarea>
       <gr-formatted-text class="message"
           content="[[comment.message]]"
+          no-trailing-margin="[[!comment.__draft]]"
           collapsed="[[collapsed]]"
           config="[[projectConfig.commentlinks]]"></gr-formatted-text>
       <div hidden$="[[!comment.robot_run_id]]">
@@ -229,11 +230,6 @@
         </div>
       </div>
       <div class="actions humanActions" hidden$="[[!_showHumanActions]]">
-        <gr-button class="action reply" on-tap="_handleReply">Reply</gr-button>
-        <gr-button class="action quote" on-tap="_handleQuote">Quote</gr-button>
-        <gr-button class="action ack" on-tap="_handleAck">Ack</gr-button>
-        <gr-button class="action done" on-tap="_handleDone">
-          Done</gr-button>
         <gr-button class="action edit hideOnPublished" on-tap="_handleEdit">
             Edit</gr-button>
         <gr-button class="action save hideOnPublished" on-tap="_handleSave"
@@ -254,7 +250,9 @@
         </div>
       </div>
       <div class="actions robotActions" hidden$="[[!_showRobotActions]]">
-        <gr-button class="action fix" on-tap="_handleFix">
+        <gr-button class="action fix"
+            on-tap="_handleFix"
+            disabled="[[robotButtonDisabled]]">
           Please Fix
         </gr-button>
       </div>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.js b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.js
index bc12b8f..1e6eef8 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.js
@@ -20,24 +20,6 @@
     is: 'gr-diff-comment',
 
     /**
-     * Fired when the Reply action is triggered.
-     *
-     * @event create-reply-comment
-     */
-
-    /**
-     * Fired when the Ack action is triggered.
-     *
-     * @event create-ack-comment
-     */
-
-    /**
-     * Fired when the Done action is triggered.
-     *
-     * @event create-done-comment
-     */
-
-    /**
      * Fired when the create fix comment action is triggered.
      *
      * @event create-fix-comment
@@ -107,6 +89,7 @@
         observer: '_toggleCollapseClass',
       },
       projectConfig: Object,
+      robotButtonDisabled: Boolean,
 
       _xhrPromise: Object,  // Used for testing.
       _messageText: {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment_test.html b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment_test.html
index 6ea5b96..1196c1b 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment_test.html
@@ -98,37 +98,6 @@
           'header middle content is not visible');
     });
 
-    test('proper event fires on reply', function(done) {
-      element.addEventListener('create-reply-comment', function(e) {
-        assert.ok(e.detail.comment);
-        done();
-      });
-      MockInteractions.tap(element.$$('.reply'));
-    });
-
-    test('proper event fires on quote', function(done) {
-      element.addEventListener('create-reply-comment', function(e) {
-        assert.ok(e.detail.comment);
-        assert.isTrue(e.detail.quote);
-        done();
-      });
-      MockInteractions.tap(element.$$('.quote'));
-    });
-
-    test('proper event fires on ack', function(done) {
-      element.addEventListener('create-ack-comment', function(e) {
-        done();
-      });
-      MockInteractions.tap(element.$$('.ack'));
-    });
-
-    test('proper event fires on done', function(done) {
-      element.addEventListener('create-done-comment', function(e) {
-        done();
-      });
-      MockInteractions.tap(element.$$('.done'));
-    });
-
     test('clicking on date link does not trigger nav', function() {
       var showStub = sinon.stub(page, 'show');
       var dateEl = element.$$('.date');
@@ -286,10 +255,6 @@
       assert.isTrue(isVisible(element.$$('.discard')), 'discard is visible');
       assert.isFalse(isVisible(element.$$('.save')), 'save is not visible');
       assert.isFalse(isVisible(element.$$('.cancel')), 'cancel is not visible');
-      assert.isFalse(isVisible(element.$$('.reply')), 'reply is not visible');
-      assert.isFalse(isVisible(element.$$('.quote')), 'quote is not visible');
-      assert.isFalse(isVisible(element.$$('.ack')), 'ack is not visible');
-      assert.isFalse(isVisible(element.$$('.done')), 'done is not visible');
       assert.isFalse(isVisible(element.$$('.resolve')),
           'resolve is not visible');
       assert.isFalse(element.$$('.humanActions').hasAttribute('hidden'));
@@ -300,10 +265,6 @@
       assert.isTrue(isVisible(element.$$('.discard')), 'discard is visible');
       assert.isTrue(isVisible(element.$$('.save')), 'save is visible');
       assert.isFalse(isVisible(element.$$('.cancel')), 'cancel is visible');
-      assert.isFalse(isVisible(element.$$('.reply')), 'reply is not visible');
-      assert.isFalse(isVisible(element.$$('.quote')), 'quote is not visible');
-      assert.isFalse(isVisible(element.$$('.ack')), 'ack is not visible');
-      assert.isFalse(isVisible(element.$$('.done')), 'done is not visible');
       assert.isTrue(isVisible(element.$$('.resolve')), 'resolve is visible');
       assert.isFalse(element.$$('.humanActions').hasAttribute('hidden'));
       assert.isTrue(element.$$('.robotActions').hasAttribute('hidden'));
@@ -315,10 +276,6 @@
           'discard is not visible');
       assert.isFalse(isVisible(element.$$('.save')), 'save is not visible');
       assert.isFalse(isVisible(element.$$('.cancel')), 'cancel is not visible');
-      assert.isTrue(isVisible(element.$$('.reply')), 'reply is visible');
-      assert.isTrue(isVisible(element.$$('.quote')), 'quote is visible');
-      assert.isTrue(isVisible(element.$$('.ack')), 'ack is visible');
-      assert.isTrue(isVisible(element.$$('.done')), 'done is visible');
       assert.isFalse(element.$$('.humanActions').hasAttribute('hidden'));
       assert.isTrue(element.$$('.robotActions').hasAttribute('hidden'));
 
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
index eea27db..8d54b2a 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
@@ -458,9 +458,10 @@
 
       promises.push(this._getChangeDetail(this._changeNum));
 
-      Promise.all(promises)
-          .then(function() { return this.$.diff.reload(); }.bind(this))
-          .then(function() { this._loading = false; }.bind(this));
+      Promise.all(promises).then(function() {
+        this._loading = false;
+        this.$.diff.reload();
+      }.bind(this));
 
       this._loadCommentMap().then(function(commentMap) {
         this._commentMap = commentMap;
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
index 6221481..a5147f1 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
@@ -230,7 +230,7 @@
       var threadEl = this._getOrCreateThreadAtLineRange(contentEl, patchNum,
           diffSide, side, range);
 
-      threadEl.addDraft(line, range);
+      threadEl.addOrEditDraft(line, range);
     },
 
     _addDraft: function(lineEl, opt_lineNum) {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html
index d2cdd07..117ff24 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html
@@ -236,15 +236,18 @@
 
         sandbox.stub(element.$.diffBuilder, 'createCommentThreadGroup',
             function() {
-          return document.createElement('gr-diff-comment-thread-group');
+          var threadGroup =
+              document.createElement('gr-diff-comment-thread-group');
+          threadGroup.patchForNewThreads = 1;
+          return threadGroup;
         });
 
         // No thread groups.
         assert.isNotOk(element._getThreadGroupForLine(contentEl));
 
         // A thread group gets created.
-        assert.isOk(element._getOrCreateThreadAtLineRange(contentEl, patchNum,
-            commentSide, side));
+        assert.isOk(element._getOrCreateThreadAtLineRange(contentEl,
+            patchNum, commentSide, side));
 
         // Try to fetch a thread with a different range.
         range = {
diff --git a/polygerrit-ui/app/elements/gr-app.html b/polygerrit-ui/app/elements/gr-app.html
index 6499f08..f9c2891 100644
--- a/polygerrit-ui/app/elements/gr-app.html
+++ b/polygerrit-ui/app/elements/gr-app.html
@@ -165,5 +165,5 @@
     <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
     <gr-reporting id="reporting"></gr-reporting>
   </template>
-  <script src="gr-app.js"></script>
+  <script src="gr-app.js" crossorigin="anonymous"></script>
 </dom-module>
diff --git a/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password_test.html b/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password_test.html
index e675de2..dcda26c 100644
--- a/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password_test.html
@@ -42,9 +42,6 @@
 
       stub('gr-rest-api-interface', {
         getAccount: function() { return Promise.resolve(account); },
-        getAccountHttpPassword: function() {
-          return Promise.resolve(password);
-        },
       });
 
       element = fixture('basic');
diff --git a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_test.html b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_test.html
index fbfdcc3..0d381cb 100644
--- a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_test.html
@@ -123,7 +123,6 @@
         getAccountEmails: function() { return Promise.resolve(); },
         getConfig: function() { return Promise.resolve(config); },
         getAccountGroups: function() { return Promise.resolve([]); },
-        getAccountHttpPassword: function() { return Promise.resolve(''); },
       });
       element = fixture('basic');
 
diff --git a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.html b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.html
index 8855b0a..d719f70 100644
--- a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.html
+++ b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.html
@@ -45,6 +45,7 @@
       gr-linked-text.pre {
         font-family: var(--monospace-font-family);
       }
+
     </style>
     <div id="container"></div>
   </template>
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
index 4d64a77..3d7a4d0 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
@@ -968,11 +968,6 @@
           '/topic', {topic: topic});
     },
 
-    getAccountHttpPassword: function(opt_errFn) {
-      return this._fetchSharedCacheURL('/accounts/self/password.http',
-          opt_errFn);
-    },
-
     deleteAccountHttpPassword: function() {
       return this.send('DELETE', '/accounts/self/password.http');
     },
diff --git a/polygerrit-ui/app/index.html b/polygerrit-ui/app/index.html
index 05f6119..db9a1c5 100644
--- a/polygerrit-ui/app/index.html
+++ b/polygerrit-ui/app/index.html
@@ -29,7 +29,7 @@
 <link rel="stylesheet" href="/styles/fonts.css">
 <link rel="stylesheet" href="/styles/main.css">
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
-<link rel="preload" href="/elements/gr-app.js" crossorigin="anonymous">
+<link rel="preload" href="/elements/gr-app.js" as="script" crossorigin="anonymous">
 <link rel="import" href="/elements/gr-app.html">
 
 <body unresolved>
diff --git a/tools/bzl/plugin.bzl b/tools/bzl/plugin.bzl
index 6e5faf0..de82af8 100644
--- a/tools/bzl/plugin.bzl
+++ b/tools/bzl/plugin.bzl
@@ -30,6 +30,7 @@
     resources = resources,
     deps = provided_deps + deps + GWT_PLUGIN_DEPS_NEVERLINK + PLUGIN_DEPS_NEVERLINK,
     visibility = ['//visibility:public'],
+    **kwargs
   )
 
   static_jars = []
@@ -56,6 +57,7 @@
       resources = list(set(srcs + resources)),
       runtime_deps = deps + GWT_PLUGIN_DEPS,
       visibility = ['//visibility:public'],
+      **kwargs
     )
     genrule2(
       name = '%s-static' % name,
diff --git a/tools/gerrit.importorder b/tools/gerrit.importorder
deleted file mode 100644
index 398130e..0000000
--- a/tools/gerrit.importorder
+++ /dev/null
@@ -1,12 +0,0 @@
-#Organize Import Order
-#Mon Mar 23 17:27:34 PDT 2015
-9=javax
-8=java
-7=org
-6=net
-5=junit
-4=eu
-3=dk
-2=com
-1=com.google
-0=\#